Instrukcje Switch są zazwyczaj szybsze niż równoważne instrukcje if-else-if (jak na przykład opisano w tym artykule ) ze względu na optymalizacje kompilatora.
Jak właściwie działa ta optymalizacja? Czy ktoś ma dobre wytłumaczenie?
Instrukcje Switch są zazwyczaj szybsze niż równoważne instrukcje if-else-if (jak na przykład opisano w tym artykule ) ze względu na optymalizacje kompilatora.
Jak właściwie działa ta optymalizacja? Czy ktoś ma dobre wytłumaczenie?
Odpowiedzi:
Kompilator może tworzyć tabele skoków, jeśli ma to zastosowanie. Na przykład, kiedy użyjesz reflektora do spojrzenia na wyprodukowany kod, zobaczysz, że dla dużych przełączników na łańcuchach kompilator faktycznie wygeneruje kod, który używa tablicy skrótów do ich wysłania. Tablica skrótów używa ciągów jako kluczy i delegatów do case
kodów jako wartości.
Ma to asymptotycznie lepszy czas wykonywania niż wiele if
testów łańcuchowych i jest w rzeczywistości szybsze nawet dla stosunkowo niewielu ciągów.
Jest to niewielkie uproszczenie, ponieważ zwykle każdy nowoczesny kompilator, który napotyka if..else if ..
sekwencję, którą można by w trywialny sposób przekonwertować na instrukcję switch przez osobę, kompilator również to zrobi. Ale tylko po to, aby zapewnić dodatkową zabawę, kompilator nie jest ograniczony składnią, więc może wewnętrznie generować instrukcje podobne do "przełączania", które mają mieszankę zakresów, pojedynczych celów itp. - i mogą (i robią) to zrobić zarówno dla switch, jak i if. .else.
W każdym razie rozszerzeniem odpowiedzi Konrada jest to, że kompilator może wygenerować tablicę skoków, ale niekoniecznie jest to gwarantowane (ani pożądane). Z różnych powodów tabele skoków źle wpływają na predyktory gałęzi na nowoczesnych procesorach, a same tabele źle wpływają na zachowanie pamięci podręcznej, np.
switch(a) { case 0: ...; break; case 1: ...; break; }
Gdyby kompilator faktycznie wygenerował dla tego tabelę skoków, prawdopodobnie byłby wolniejszy niż if..else if..
kod alternatywnego stylu, ponieważ tabela skoków pokonałaby przewidywanie gałęzi.
Statystyki braku meczów mogą nie być dobre.
Jeśli faktycznie pobierasz źródło, wiadomo, że wartości no match to 21, zarówno w przypadku if, jak i switch. Kompilator powinien mieć możliwość abstrakcji, wiedząc, która instrukcja powinna być wykonywana przez cały czas, a procesor powinien być w stanie prawidłowo przewidywać rozgałęzienia.
Bardziej interesujący jest przypadek, gdy moim zdaniem nie każdy przypadek się załamuje, ale może nie był to zakres eksperymentu.
Instrukcje switch / case mogą być zwykle szybsze o 1 poziom, ale gdy zaczniesz wchodzić w 2 lub więcej, instrukcje switch / case zaczynają zajmować 2-3 razy dłużej niż zagnieżdżone instrukcje if / else.
Ten artykuł zawiera porównania szybkości, które podkreślają różnice w szybkości w przypadku zagnieżdżania takich instrukcji.
Na przykład, zgodnie z ich testami, przykładowy kod podobny do poniższego:
if (x % 3 == 0)
if (y % 3 == 0)
total += 3;
else if (y % 3 == 1)
total += 2;
else if (y % 3 == 2)
total += 1;
else
total += 0;
else if (x % 3 == 1)
if (y % 3 == 0)
total += 3;
else if (y % 3 == 1)
total += 2;
else if (y % 3 == 2)
total += 1;
else
total += 0;
else if (x % 3 == 2)
if (y % 3 == 0)
total += 3;
else if (y % 3 == 1)
total += 2;
else if (y % 3 == 2)
total += 1;
else
total += 0;
else
if (y % 3 == 0)
total += 3;
else if (y % 3 == 1)
total += 2;
else if (y % 3 == 2)
total += 1;
else
total += 0;
zakończyło się o połowę krócej niż wykonanie równoważnej instrukcji switch / case:
switch (x % 3)
{
case 0:
switch (y % 3)
{
case 0: total += 3;
break;
case 1: total += 2;
break;
case 2: total += 1;
break;
default: total += 0;
break;
}
break;
case 1:
switch (y % 3)
{
case 0: total += 3;
break;
case 1: total += 2;
break;
case 2: total += 1;
break;
default: total += 0;
break;
}
break;
case 2:
switch (y % 3)
{
case 0: total += 3;
break;
case 1: total += 2;
break;
case 2: total += 1;
break;
default: total += 0;
break;
}
break;
default:
switch (y % 3)
{
case 0: total += 3;
break;
case 1: total += 2;
break;
case 2: total += 1;
break;
default: total += 0;
break;
}
break;
}
Tak, to szczątkowy przykład, ale ilustruje to.
Więc wniosek może być taki, że użyj przełącznika / przypadku dla prostych typów, które mają tylko jeden poziom głębokości, ale w przypadku bardziej złożonych porównań i wielu zagnieżdżonych poziomów użyj klasycznych konstrukcji if / else?