Jaka jest najlepsza praktyka używania switch
instrukcji w porównaniu z użyciem if
instrukcji dla 30 unsigned
wyliczeń, 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();
}
Odpowiedzi:
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ń).
źródło
if
-else
łańcuchów w metaprogramowaniu szablonów i trudności w generowaniuswitch
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ępnegoW specjalnym przypadku, który podałeś w swoim przykładzie, prawdopodobnie najwyraźniejszy kod to:
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:
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.
źródło
const std::bitset<MAXERR> specialerror(initializer);
użyj go zif (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 kompilatorswitch
, zwł. w nietypowym przypadku, gdy będzie to jedna nieobjęta gałąź.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 tejif
wersji. Ponadto nowsze gcc użyją instrukcji bit-testbt
zamiast przesuwania, aby umieścić1
bit we właściwym miejscu i użyćtest reg, imm32
.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:
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:
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 ...)
.
źródło
Kompilator i tak go zoptymalizuje - wybierz przełącznik, ponieważ jest najbardziej czytelny.
źródło
gcc
na pewno tego nie zrobi (jest ku temu dobry powód). Clang zoptymalizuje oba przypadki do wyszukiwania binarnego. Na przykład zobacz to .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.
źródło
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ą.
źródło
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!)
źródło
Kompilatory są naprawdę dobre w optymalizacji
switch
. Najnowsze gcc jest również dobre w optymalizacji szeregu warunków wif
.Zrobiłem kilka przypadków testowych na godbolt .
Kiedy
case
wartoś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
switch
to (iif
coś bardzo podobnego):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
switch
do bitmapy, ale robi to1U<<errNumber
z mov / shift. Kompilujeif
wersję do serii gałęzi.Zwróć uwagę, jak odejmuje 1 od
errNumber
(lea
by 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:
(Brak użycia
jc fire_special_event
jest wszechobecny i jest błędem kompilatora ).rep ret
jest 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 oerrNumber
bity 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ż
bt
w ostatnim Intelu (2 uops zamiast 1, a shift nie robi wszystkiego innego, co jest potrzebne).źródło
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.
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.
Wartości w przełączniku są definiowane przez użytkownika, podczas gdy wartości w przypadku if else są oparte na ograniczeniach.
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.
Obudowa przełącznika jest znacznie bardziej kompaktowa i łatwa do odczytania i zrozumienia.
źródło
IMO to doskonały przykład tego, do czego służy przelot przełącznika.
źródło
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.
źródło
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.
źródło
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
if
stanu 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.
źródło
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 .
źródło
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ś,
if
co jest prawdą! Posiadanie przełącznika jednym ruchem wydaje się trochę ... niepotrzebne.źródło
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.
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.
źródło
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.
źródło
std::bitset<MAXERR> specialerror;
toif (specialerror[err]) { special_handler(); }
. Będzie to szybsze niż tabela skoków, zwł. w przypadku nieobjętym.Nie jestem pewien co do najlepszych praktyk, ale użyłbym przełącznika - a następnie pułapkę celowego przejścia przez „default”
źródło
Z estetycznego punktu widzenia preferuję takie podejście.
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):
źródło
break
zewnętrznego acase
(tak, pomocniczy grzech, ale mimo wszystko grzech). :)Prawdopodobnie pierwsza jest optymalizowana przez kompilator, co wyjaśniałoby, dlaczego druga pętla jest wolniejsza przy zwiększaniu liczby pętli.
źródło
if
porównaniuswitch
z warunkiem zakończenia pętli w Javie.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:
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:
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 ().
źródło
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)
źródło
Wiem, że to stare, ale
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)
źródło
if(max) break
Pę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 docurrentTimeMillis
ma więcej narzutów, ponieważ nie wszystko jest jeszcze skompilowane w JIT? Ustawienie pętli w innej kolejności prawdopodobnie dałoby inne rezultaty.