Zaleta przełączenia instrukcji if-else


168

Jaka jest najlepsza praktyka używania switchinstrukcji w porównaniu z użyciem ifinstrukcji dla 30 unsignedwyliczeń, w których około 10 ma oczekiwaną akcję (to jest obecnie ta sama akcja). Należy wziąć pod uwagę wydajność i przestrzeń, ale nie są one krytyczne. Wyodrębniłem fragment, więc nie nienawidź mnie za konwencje nazewnictwa.

switch komunikat:

// numError is an error enumeration type, with 0 being the non-error case
// fire_special_event() is a stub method for the shared processing

switch (numError)
{  
  case ERROR_01 :  // intentional fall-through
  case ERROR_07 :  // intentional fall-through
  case ERROR_0A :  // intentional fall-through
  case ERROR_10 :  // intentional fall-through
  case ERROR_15 :  // intentional fall-through
  case ERROR_16 :  // intentional fall-through
  case ERROR_20 :
  {
     fire_special_event();
  }
  break;

  default:
  {
    // error codes that require no additional action
  }
  break;       
}

if komunikat:

if ((ERROR_01 == numError)  ||
    (ERROR_07 == numError)  ||
    (ERROR_0A == numError)  || 
    (ERROR_10 == numError)  ||
    (ERROR_15 == numError)  ||
    (ERROR_16 == numError)  ||
    (ERROR_20 == numError))
{
  fire_special_event();
}

26
To jest redagowane jako „subiektywne”? Naprawdę? Z pewnością „subiektywne” dotyczy rzeczy, których nie można udowodnić w taki czy inny sposób?
Alexandra Franks,

Jasne, widać to z punktu, w którym generuje najbardziej wydajny kod, ale każdy nowoczesny kompilator powinien być równie wydajny. W końcu jest to bardziej kwestia koloru szopy na rower.
jfs

8
Nie zgadzam się, nie sądzę, żeby to było subiektywne. Prosta różnica w ASM ma znaczenie, w wielu przypadkach nie można po prostu pominąć kilku sekund optymalizacji. W tym pytaniu nie jest to wojna religijna ani debata, istnieje racjonalne wyjaśnienie, dlaczego ktoś miałby być szybszy, wystarczy przeczytać zaakceptowaną odpowiedź.
chakrit


@RichardFranks offtopic: Grats! jesteś pierwszym człowiekiem przejętym moderacją na SO, jakie kiedykolwiek widziałem
jungle_mole,

Odpowiedzi:


162

Użyj przełącznika.

W najgorszym przypadku kompilator wygeneruje taki sam kod jak łańcuch if-else, więc nic nie stracisz. Jeśli masz wątpliwości, najpierw umieść najczęstsze przypadki w instrukcji przełączania.

W najlepszym przypadku optymalizator może znaleźć lepszy sposób na wygenerowanie kodu. Typowe rzeczy, które robi kompilator, to budowanie binarnego drzewa decyzyjnego (zapisuje porównania i skoki w przeciętnym przypadku) lub po prostu budowanie tabeli skoków (działa bez porównań).


2
Technicznie nadal będzie jedno porównanie, aby upewnić się, że wartość wyliczenia znajduje się w tabeli skoków.
0124816,

Jep. To prawda. Włączenie wyliczeń i obsługa wszystkich przypadków może jednak pozbyć się ostatniego porównania.
Nils Pipenbrinck,

4
Zwróć uwagę, że teoretycznie można przeanalizować serię „if”, aby była taka sama, jak przełączanie przez kompilator, ale po co ryzykować? Używając przełącznika, przekazujesz dokładnie to, czego chcesz, co ułatwia generowanie kodu.
jakobengblom2

5
jakoben: Można to zrobić, ale tylko dla łańcuchów if / else podobnych do przełącznika. W praktyce nie występują one, ponieważ programiści używają przełącznika. Zagłębiłem się w technologię kompilatorów i zaufaj mi: znajdowanie takich „bezużytecznych” konstrukcji zajmuje dużo czasu. Dla kompilatorów taka optymalizacja ma sens.
Nils Pipenbrinck

5
@NilsPipenbrinck z łatwością budowania pseudo-rekurencyjnych if- elsełańcuchów w metaprogramowaniu szablonów i trudności w generowaniu switch casełańcuchów, że mapowanie może stać się ważniejsze. (i tak, starożytny komentarz, ale sieć jest wieczna lub przynajmniej do następnego
wtorku

45

W specjalnym przypadku, który podałeś w swoim przykładzie, prawdopodobnie najwyraźniejszy kod to:

if (RequiresSpecialEvent(numError))
    fire_special_event();

Oczywiście to po prostu przenosi problem do innego obszaru kodu, ale teraz masz możliwość ponownego wykorzystania tego testu. Masz również więcej możliwości rozwiązania tego problemu. Możesz użyć std :: set, na przykład:

bool RequiresSpecialEvent(int numError)
{
    return specialSet.find(numError) != specialSet.end();
}

Nie sugeruję, że jest to najlepsza implementacja RequireSpecialEvent, tylko że jest to opcja. Nadal możesz użyć przełącznika, łańcucha if-else, tabeli przeglądowej lub jakiejś manipulacji bitami na wartości, cokolwiek. Im bardziej niejasny staje się proces decyzyjny, tym większą wartość uzyskasz, mając go w izolowanej funkcji.


5
To jest takie prawdziwe. Czytelność jest o wiele lepsza niż zarówno przełącznik, jak i instrukcje if. Właściwie to sam chciałem odpowiedzieć na coś takiego, ale pokonałeś mnie. :-)
mlarsen

Jeśli wszystkie wartości wyliczenia są małe, nie potrzebujesz skrótu, tylko tabeli. np. const std::bitset<MAXERR> specialerror(initializer); użyj go z if (specialerror[numError]) { fire_special_event(); }. Jeśli chcesz sprawdzić granice, bitset::test(size_t)zgłosi wyjątek dla wartości spoza zakresu. ( bitset::operator[]nie sprawdza zakresu). cplusplus.com/reference/bitset/bitset/test . Prawdopodobnie będzie to lepsze od implementacji tabeli skoków wygenerowanej przez kompilator switch, zwł. w nietypowym przypadku, gdy będzie to jedna nieobjęta gałąź.
Peter Cordes

@PeterCordes Nadal twierdzę, że lepiej jest umieścić tabelę w jej własnej funkcji. Jak powiedziałem, istnieje wiele opcji, które otwierają się, gdy to robisz, nie próbowałem ich wszystkich wyliczać.
Mark Ransom

@MarkRansom: Nie chciałem nie zgodzić się z abstrakcją. Odkąd podałeś przykładową implementację przy użyciu std::set, pomyślałem, że wskazałem, że prawdopodobnie jest to zły wybór. Okazuje się, że gcc już kompiluje kod OP, aby natychmiast przetestować bitmapę w 32-bitowej wersji. godbolt: goo.gl/qjjv0e . gcc 5.2 zrobi to nawet dla tej ifwersji. Ponadto nowsze gcc użyją instrukcji bit-test btzamiast przesuwania, aby umieścić 1bit we właściwym miejscu i użyć test reg, imm32.
Peter Cordes

Ta natychmiastowa stała bitmapa to duża wygrana, ponieważ bitmapa nie zawiera żadnych braków w pamięci podręcznej. Działa, jeśli wszystkie „specjalne” kody błędów mieszczą się w zakresie 64 lub mniej. (lub 32 dla starszego kodu 32-bitowego). Kompilator odejmuje wartość najmniejszej wielkości liter, jeśli jest różna od zera. Wniosek jest taki, że najnowsze kompilatory są na tyle inteligentne, że prawdopodobnie uzyskasz dobry kod z dowolnej logiki, której używasz, chyba że powiesz mu, aby używał obszernej struktury danych.
Peter Cordes

24

Zmiana jest szybsza.

Po prostu spróbuj użyć 30 różnych wartości wewnątrz pętli i porównaj je z tym samym kodem za pomocą przełącznika, aby zobaczyć, o ile szybszy jest przełącznik.

Teraz przełącznik ma jeden prawdziwy problem : przełącznik musi znać w czasie kompilacji wartości wewnątrz każdego przypadku. Oznacza to, że następujący kod:

// WON'T COMPILE
extern const int MY_VALUE ;

void doSomething(const int p_iValue)
{
    switch(p_iValue)
    {
       case MY_VALUE : /* do something */ ; break ;
       default : /* do something else */ ; break ;
    }
}

nie skompiluje się.

Większość ludzi użyje wtedy definicji (Aargh!), A inni będą deklarować i definiować zmienne stałe w tej samej jednostce kompilacji. Na przykład:

// WILL COMPILE
const int MY_VALUE = 25 ;

void doSomething(const int p_iValue)
{
    switch(p_iValue)
    {
       case MY_VALUE : /* do something */ ; break ;
       default : /* do something else */ ; break ;
    }
}

Ostatecznie więc programista musi wybrać pomiędzy „szybkością + przejrzystość” a „sprzężeniem kodu”.

(Nie znaczy to, że przełącznik nie może być napisany jako mylący jak diabli ... Większość przełączników, które obecnie widzę, należy do tej „zagmatwanej” kategorii ... Ale to już inna historia ...)

Edycja 2008-09-21:

bk1e dodał następujący komentarz: " Definiowanie stałych jako wyliczeń w pliku nagłówkowym jest innym sposobem radzenia sobie z tym".

Oczywiście, że jest.

Celem typu zewnętrznego było oddzielenie wartości od źródła. Zdefiniowanie tej wartości jako makra, prostej deklaracji stałej int lub nawet wyliczenia ma efekt uboczny polegający na wstawieniu wartości. W związku z tym, gdyby definicja, wartość wyliczenia lub stała wartość int uległy zmianie, konieczna byłaby ponowna kompilacja. Deklaracja extern oznacza, że ​​nie ma potrzeby rekompilacji w przypadku zmiany wartości, ale z drugiej strony uniemożliwia użycie przełącznika. Wniosek, że użycie przełącznika zwiększy sprzężenie między kodem przełącznika a zmiennymi używanymi jako przypadki . Kiedy wszystko jest w porządku, użyj przełącznika. Jeśli tak nie jest, nic dziwnego.

.

Edycja 2013-01-15:

Vlad Lazarenko skomentował moją odpowiedź, podając link do jego dogłębnej analizy kodu asemblera generowanego przez przełącznik. Bardzo pouczające: http://lazarenko.me/switch/


Definiowanie stałych jako wyliczeń w pliku nagłówkowym jest innym sposobem radzenia sobie z tym problemem.
bk1e


1
@Vlad Lazarenko: Dzięki za link! To była bardzo ciekawa lektura.
paercebal

1
Link @AhmedHussein user404725 jest martwy. Na szczęście znalazłem go w WayBack Machine: web.archive.org/web/20131111091431/http://lazarenko.me/2013/01/… . Rzeczywiście, WayBack Machine może być sporym błogosławieństwem.
Jack Giffin

Wielkie dzięki, to bardzo pomocne
Ahmed Hussein

20

Kompilator i tak go zoptymalizuje - wybierz przełącznik, ponieważ jest najbardziej czytelny.


3
Są szanse, że kompilator nie dotknie if-then-else. W rzeczywistości gccna pewno tego nie zrobi (jest ku temu dobry powód). Clang zoptymalizuje oba przypadki do wyszukiwania binarnego. Na przykład zobacz to .

7

Switch, choćby dla czytelności. Gigantyczne, jeśli stwierdzenia są trudniejsze do utrzymania, a moim zdaniem trudniejsze do odczytania.

ERROR_01 : // celowe przejście

lub

(ERROR_01 == numError) ||

Ta ostatnia jest bardziej podatna na błędy i wymaga więcej wpisywania i formatowania niż pierwsza.


6

Kod czytelności. Jeśli chcesz wiedzieć, co działa lepiej, użyj profilera, ponieważ optymalizacje i kompilatory są różne, a problemy z wydajnością rzadko występują tam, gdzie ludzie myślą.


6

Użyj przełącznika, do tego służy i czego oczekują programiści.

Umieściłbym jednak zbędne etykiety na skrzynkach - żeby ludzie czuli się komfortowo, starałem się przypomnieć sobie, kiedy / jakie są zasady ich pomijania.
Nie chcesz, aby następny programista pracujący nad tym musiał niepotrzebnie zastanawiać się nad szczegółami językowymi (możesz to być Ty za kilka miesięcy!)


4

Kompilatory są naprawdę dobre w optymalizacji switch. Najnowsze gcc jest również dobre w optymalizacji szeregu warunków w if.

Zrobiłem kilka przypadków testowych na godbolt .

Kiedy casewartości są zgrupowane blisko siebie, gcc, clang i icc są wystarczająco inteligentne, aby użyć mapy bitowej, aby sprawdzić, czy wartość jest jedną z tych specjalnych.

np. gcc 5.2 -O3 kompiluje switchto (i ifcoś bardzo podobnego):

errhandler_switch(errtype):  # gcc 5.2 -O3
    cmpl    $32, %edi
    ja  .L5
    movabsq $4301325442, %rax   # highest set bit is bit 32 (the 33rd bit)
    btq %rdi, %rax
    jc  .L10
.L5:
    rep ret
.L10:
    jmp fire_special_event()

Zauważ, że mapa bitowa to dane natychmiastowe, więc nie ma potencjalnego braku dostępu do niej w pamięci podręcznej danych lub tabeli skoków.

gcc 4.9.2 -O3 kompiluje switchdo bitmapy, ale robi to 1U<<errNumberz mov / shift. Kompiluje ifwersję do serii gałęzi.

errhandler_switch(errtype):  # gcc 4.9.2 -O3
    leal    -1(%rdi), %ecx
    cmpl    $31, %ecx    # cmpl $32, %edi  wouldn't have to wait an extra cycle for lea's output.
              # However, register read ports are limited on pre-SnB Intel
    ja  .L5
    movl    $1, %eax
    salq    %cl, %rax   # with -march=haswell, it will use BMI's shlx to avoid moving the shift count into ecx
    testl   $2150662721, %eax
    jne .L10
.L5:
    rep ret
.L10:
    jmp fire_special_event()

Zwróć uwagę, jak odejmuje 1 od errNumber( leaby połączyć tę operację z ruchem). To pozwala natychmiast dopasować bitmapę do 32-bitowej wersji, unikając natychmiastowej 64-bitowejmovabsq która zajmuje więcej bajtów instrukcji.

Krótsza (w kodzie maszynowym) sekwencja to:

    cmpl    $32, %edi
    ja  .L5
    mov     $2150662721, %eax
    dec     %edi   # movabsq and btq is fewer instructions / fewer Intel uops, but this saves several bytes
    bt     %edi, %eax
    jc  fire_special_event
.L5:
    ret

(Brak użycia jc fire_special_eventjest wszechobecny i jest błędem kompilatora ).

rep retjest używany w gałęziach docelowych i kolejnych gałęziach warunkowych na korzyść starych AMD K8 i K10 (pre-Bulldozer): Co oznacza „rep ret”? . Bez tego przewidywanie gałęzi nie działa tak dobrze na tych przestarzałych procesorach.

bt(test bitowy) z rejestrem arg jest szybki. Łączy pracę przesuwania w lewo 1 o errNumberbity i robienia atest , ale nadal jest opóźnieniem 1 cyklu i tylko pojedynczym uopem Intel. Jest powolny z argumentem pamięci ze względu na jego zbyt dużą semantykę CISC: z operandem pamięci dla „ciągu bitowego” adres bajtu do testowania jest obliczany na podstawie drugiego argumentu (podzielonego przez 8) i nie jest nie ogranicza się do 1, 2, 4 lub 8-bajtowego fragmentu wskazywanego przez operand pamięci.

Z tabel instrukcji Agner Fog, instrukcja zmiany liczby zmiennej jest wolniejsza niż btw ostatnim Intelu (2 uops zamiast 1, a shift nie robi wszystkiego innego, co jest potrzebne).


4

Zalety metody switch () w porównaniu z przypadkiem if else to: - 1. Przełącznik jest znacznie wydajniejszy w porównaniu do przypadku if else, ponieważ każdy przypadek nie zależy od przypadku poprzedniego, inaczej niż w przypadku if else, w którym poszczególne instrukcje muszą być sprawdzane pod kątem warunku prawdziwości lub fałszu.

  1. Kiedy jest nie. wartości dla pojedynczego wyrażenia, zmiana wielkości liter jest bardziej elastyczna w stosunku do if else, ponieważ w przypadku if else ocena przypadku opiera się tylko na dwóch wartościach, tj. albo prawda, albo fałsz.

  2. Wartości w przełączniku są definiowane przez użytkownika, podczas gdy wartości w przypadku if else są oparte na ograniczeniach.

  3. W przypadku błędu, stwierdzenia w przełączniku można łatwo sprawdzić krzyżowo i poprawić, co jest stosunkowo trudne do sprawdzenia w przypadku stwierdzenia if else.

  4. Obudowa przełącznika jest znacznie bardziej kompaktowa i łatwa do odczytania i zrozumienia.


2

IMO to doskonały przykład tego, do czego służy przelot przełącznika.


w c # jest to jedyny przypadek, w którym zachodzi myśl o upadku. Dobry argument.
BCS,

2

Jeśli Twoje przypadki prawdopodobnie pozostaną pogrupowane w przyszłości - jeśli więcej niż jeden przypadek odpowiada jednemu wynikowi - przełącznik może okazać się łatwiejszy do odczytania i utrzymania.


2

Działają równie dobrze. Wydajność jest mniej więcej taka sama, biorąc pod uwagę nowoczesny kompilator.

Wolę instrukcje if zamiast instrukcji case, ponieważ są bardziej czytelne i bardziej elastyczne - możesz dodać inne warunki nie oparte na równości liczbowej, takie jak „|| max <min”. Ale w przypadku prostego przypadku, który tutaj opublikowałeś, nie ma to większego znaczenia, po prostu zrób to, co jest dla Ciebie najbardziej czytelne.


2

przełącznik jest zdecydowanie preferowany. Łatwiej jest spojrzeć na listę przypadków przełącznika i wiedzieć na pewno, co robi, niż przeczytać warunek long if.

Powielanie tego ifstanu jest trudne dla oczu. Załóżmy, że jeden z nich ==został napisany!= ; zauważyłeś? Lub jeśli jedno wystąpienie „numError” zostało zapisane jako „nmuError”, co akurat się skompilowało?

Generalnie wolałbym użyć polimorfizmu zamiast przełącznika, ale bez dodatkowych szczegółów kontekstu trudno powiedzieć.

Jeśli chodzi o wydajność, najlepszym rozwiązaniem jest użycie profilera do pomiaru wydajności aplikacji w warunkach podobnych do tych, których oczekujesz w środowisku naturalnym. W przeciwnym razie prawdopodobnie dokonujesz optymalizacji w niewłaściwym miejscu i w niewłaściwy sposób.


2

Zgadzam się z kompatybilnością rozwiązania przełącznika, ale IMO przejmujesz tutaj przełącznik .
Celem przełącznika jest różna obsługa w zależności od wartości.
Gdybyś musiał wyjaśnić swoje algo w pseudokodzie, użyłbyś if ponieważ, semantycznie, to jest to: jeśli cokolwiek_error zrobi to ...
Więc chyba że kiedyś zamierzasz zmienić swój kod, aby miał określony kod dla każdego błędu , Użyłbym if .


2
Nie zgadzam się z tego samego powodu, z którego nie zgadzam się z przypadkiem upadku. Czytam przełącznik jako „W przypadkach 01,07,0A, 10,15,16 i 20 pożaru zdarzenie specjalne”. Nie ma jednak przejścia do innej sekcji., To tylko artefakt składni C ++, w którym powtarzasz słowo kluczowe „case” dla każdej wartości.
MSalters,

1

Wybrałbym stwierdzenie if ze względu na przejrzystość i konwencję, chociaż jestem pewien, że niektórzy się z tym nie zgodzą. W końcu chcesz zrobić coś, ifco jest prawdą! Posiadanie przełącznika jednym ruchem wydaje się trochę ... niepotrzebne.


1

Powiedziałbym, że użyj SWITCH. W ten sposób wystarczy wdrożyć różne wyniki. Twoje dziesięć identycznych przypadków może używać wartości domyślnej. Jeśli jedna zmiana wystarczy, aby jawnie wprowadzić zmianę, nie ma potrzeby edytowania wartości domyślnej. O wiele łatwiej jest również dodawać i usuwać przypadki z SWITCH niż edytować IF i ELSEIF.

switch(numerror){
    ERROR_20 : { fire_special_event(); } break;
    default : { null; } break;
}

Może nawet przetestuj swój stan (w tym przypadku numerror) z listą możliwości, być może tablicą, aby Twój przełącznik SWITCH nie był nawet używany, chyba że na pewno będzie wynik.


Łącznie jest około 30 błędów. 10 wymaga specjalnej akcji, więc używam wartości domyślnych dla ~ 20 błędów, które nie wymagają akcji ...
Zing-

1

Widząc, że masz tylko 30 kodów błędów, zakoduj własną tabelę skoków, a następnie samodzielnie dokonasz wszystkich wyborów optymalizacyjnych (skok zawsze będzie najszybszy), zamiast mieć nadzieję, że kompilator zrobi to, co trzeba. Sprawia również, że kod jest bardzo mały (poza statyczną deklaracją tabeli skoków). Ma również tę dodatkową zaletę, że za pomocą debuggera można modyfikować zachowanie w czasie wykonywania, jeśli zajdzie taka potrzeba, po prostu przez bezpośrednie wstawianie danych tabeli.


Wow, to wydaje się być sposobem na przekształcenie prostego problemu w złożony. Po co zadawać sobie tyle trudu, skoro kompilator wykona dla ciebie świetną robotę. Poza tym najwyraźniej jest to program obsługi błędów, więc prawdopodobnie nie będzie tak krytyczny dla szybkości. Przełącznik jest zdecydowanie najłatwiejszy do odczytania i utrzymania.
MrZebra,

Tabela nie jest skomplikowana - w rzeczywistości jest prawdopodobnie prostsza niż przejście na kod. W oświadczeniu wspomniano, że czynnikiem była wydajność.
Greg Whitfield

To brzmi jak przedwczesna optymalizacja. Dopóki utrzymujesz małe i ciągłe wartości wyliczenia, kompilator powinien zrobić to za Ciebie. Umieszczenie przełącznika w osobnej funkcji sprawia, że ​​kod, który go używa, jest ładny i mały, jak sugeruje Mark Ransom w swojej odpowiedzi, daje tę samą korzyść dla małego kodu.
Peter Cordes

Ponadto, jeśli zamierzasz wdrożyć cokolwiek samodzielnie, zrób std::bitset<MAXERR> specialerror;to if (specialerror[err]) { special_handler(); }. Będzie to szybsze niż tabela skoków, zwł. w przypadku nieobjętym.
Peter Cordes

1

Nie jestem pewien co do najlepszych praktyk, ale użyłbym przełącznika - a następnie pułapkę celowego przejścia przez „default”


1

Z estetycznego punktu widzenia preferuję takie podejście.

unsigned int special_events[] = {
    ERROR_01,
    ERROR_07,
    ERROR_0A,
    ERROR_10,
    ERROR_15,
    ERROR_16,
    ERROR_20
 };
 int special_events_length = sizeof (special_events) / sizeof (unsigned int);

 void process_event(unsigned int numError) {
     for (int i = 0; i < special_events_length; i++) {
         if (numError == special_events[i]) {
             fire_special_event();
             break;
          }
     }
  }

Uczyń dane bardziej inteligentnymi, abyśmy mogli uczynić logikę nieco głupszą.

Zdaję sobie sprawę, że wygląda to dziwnie. Oto inspiracja (z tego, jak zrobiłbym to w Pythonie):

special_events = [
    ERROR_01,
    ERROR_07,
    ERROR_0A,
    ERROR_10,
    ERROR_15,
    ERROR_16,
    ERROR_20,
    ]
def process_event(numError):
    if numError in special_events:
         fire_special_event()

4
Składnia języka jest nie mieć wpływu na sposób możemy wdrożyć rozwiązanie ... => To wygląda brzydko w C i miłe w Pythonie. :)
rlerallut

Używać bitmap? Jeśli błąd_0a to 0x0a itp., Możesz umieścić je jako bity w długim czasie. long long special_events = 1LL << 1 | 1LL << 7 | 1LL << 0xa ... Następnie użyj if (special_events & (1LL << numError) fire_special_event ()
paperhorse

1
Fuj. Przekształciłeś operację O (1) w najgorszym przypadku (jeśli generowane są tablice skoków) w O (N) w najgorszym przypadku (gdzie N jest liczbą obsłużonych obserwacji) i użyłeś a breakzewnętrznego a case(tak, pomocniczy grzech, ale mimo wszystko grzech). :)
Mac

Fuj? Powiedział, że wydajność i przestrzeń nie są krytyczne. Po prostu zaproponowałem inny sposób spojrzenia na problem. Jeśli potrafimy przedstawić problem w sposób, w którym ludzie myślą mniej, to zwykle nie obchodzi mnie, czy oznacza to, że komputery muszą myśleć więcej.
mbac32768

1
while (true) != while (loop)

Prawdopodobnie pierwsza jest optymalizowana przez kompilator, co wyjaśniałoby, dlaczego druga pętla jest wolniejsza przy zwiększaniu liczby pętli.


Wydaje się, że jest to komentarz do odpowiedzi McAnix. To tylko jeden z problemów związanych z próbą synchronizacji w ifporównaniu switchz warunkiem zakończenia pętli w Javie.
Peter Cordes

1

Jeśli chodzi o kompilację programu, nie wiem, czy jest jakaś różnica. Ale jeśli chodzi o sam program i utrzymywanie kodu tak prostego, jak to tylko możliwe, osobiście uważam, że zależy to od tego, co chcesz zrobić. jeśli inaczej stwierdzenia if else mają swoje zalety, którymi myślę, że są:

pozwalają ci przetestować zmienną w określonych zakresach, możesz użyć funkcji (Biblioteka standardowa lub Osobista) jako warunku.

(przykład:

`int a;
 cout<<"enter value:\n";
 cin>>a;

 if( a > 0 && a < 5)
   {
     cout<<"a is between 0, 5\n";

   }else if(a > 5 && a < 10)

     cout<<"a is between 5,10\n";

   }else{

       "a is not an integer, or is not in range 0,10\n";

Jednak jeśli inaczej, stwierdzenia if else mogą się skomplikować i zabrudzić (pomimo twoich najlepszych prób) w pośpiechu. Instrukcje Switch wydają się być jaśniejsze, czystsze i łatwiejsze do odczytania; ale można go używać tylko do testowania pod kątem określonych wartości (przykład:

`int a;
 cout<<"enter value:\n";
 cin>>a;

 switch(a)
 {
    case 0:
    case 1:
    case 2: 
    case 3:
    case 4:
    case 5:
        cout<<"a is between 0,5 and equals: "<<a<<"\n";
        break;
    //other case statements
    default:
        cout<<"a is not between the range or is not a good value\n"
        break;

Wolę oświadczenia if - else if - else, ale tak naprawdę to zależy od Ciebie. Jeśli chcesz użyć funkcji jako warunków lub chcesz przetestować coś względem zakresu, tablicy lub wektora i / lub nie masz nic przeciwko skomplikowanemu zagnieżdżeniu, polecam użycie If else if else block. Jeśli chcesz testować z pojedynczymi wartościami lub chcesz mieć czysty i łatwy do odczytania blok, polecam użycie bloków switch ().


0

Nie jestem osobą, która powie Ci o szybkości i wykorzystaniu pamięci, ale patrzenie na instrukcję przełącznika jest o wiele łatwiejsze do zrozumienia niż duże oświadczenie if (szczególnie 2-3 miesiące później)


0

Wiem, że to stare, ale

public class SwitchTest {
static final int max = 100000;

public static void main(String[] args) {

int counter1 = 0;
long start1 = 0l;
long total1 = 0l;

int counter2 = 0;
long start2 = 0l;
long total2 = 0l;
boolean loop = true;

start1 = System.currentTimeMillis();
while (true) {
  if (counter1 == max) {
    break;
  } else {
    counter1++;
  }
}
total1 = System.currentTimeMillis() - start1;

start2 = System.currentTimeMillis();
while (loop) {
  switch (counter2) {
    case max:
      loop = false;
      break;
    default:
      counter2++;
  }
}
total2 = System.currentTimeMillis() - start2;

System.out.println("While if/else: " + total1 + "ms");
System.out.println("Switch: " + total2 + "ms");
System.out.println("Max Loops: " + max);

System.exit(0);
}
}

Zróżnicowanie liczby pętli bardzo się zmienia:

Podczas gdy if / else: 5 ms Switch: 1 ms Max Loops: 100000

Podczas gdy if / else: 5 ms Switch: 3 ms Max Loops: 1000000

Podczas gdy if / else: 5 ms Switch: 14 ms Max Loops: 10000000

Podczas gdy if / else: 5 ms Switch: 149 ms Max Loops: 100000000

(dodaj więcej stwierdzeń, jeśli chcesz)


3
Słuszna uwaga, ale proszę cię, stary, mówisz w złym języku. Różnorodność języka bardzo się zmienia;)
Gabriel Schreiber

2
if(max) breakPętla działa w stałym czasie, niezależnie od liczby pętli? Wygląda na to, że kompilator JIT jest wystarczająco inteligentny, aby zoptymalizować pętlę counter2=max. A może jest wolniejsze niż przełączanie, jeśli pierwsze wywołanie do currentTimeMillisma więcej narzutów, ponieważ nie wszystko jest jeszcze skompilowane w JIT? Ustawienie pętli w innej kolejności prawdopodobnie dałoby inne rezultaty.
Peter Cordes
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.