Czy "switch" jest szybszy niż "if"?
Czy switch
oświadczenierzeczywiście jest szybsze niż if
oświadczenie?
Uruchomiłem poniższy kod na kompilatorze x64 C++ Visual Studio 2010 z flagą /Ox
:
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#define MAX_COUNT (1 << 29)
size_t counter = 0;
size_t testSwitch()
{
clock_t start = clock();
size_t i;
for (i = 0; i < MAX_COUNT; i++)
{
switch (counter % 4 + 1)
{
case 1: counter += 4; break;
case 2: counter += 3; break;
case 3: counter += 2; break;
case 4: counter += 1; break;
}
}
return 1000 * (clock() - start) / CLOCKS_PER_SEC;
}
size_t testIf()
{
clock_t start = clock();
size_t i;
for (i = 0; i < MAX_COUNT; i++)
{
const size_t c = counter % 4 + 1;
if (c == 1) { counter += 4; }
else if (c == 2) { counter += 3; }
else if (c == 3) { counter += 2; }
else if (c == 4) { counter += 1; }
}
return 1000 * (clock() - start) / CLOCKS_PER_SEC;
}
int main()
{
printf("Starting...\n");
printf("Switch statement: %u ms\n", testSwitch());
printf("If statement: %u ms\n", testIf());
}
I mam te wyniki:
Switch statement: 5261 ms
If statement: 5196 ms
Z tego, co się dowiedziałem, switch
Instrukcje najwyraźniej używają tabel skoku do optymalizacji rozgałęzień.
Pytania:
-
Jaka byłaby podstawowa tabela skoków wygląda jak w x86 czy x64?
-
Czy ten kod używa tabeli skoków?
-
Dlaczego w tym przykładzie nie ma różnicy w wydajności? Czy jest jakaś sytuacja, w której jest znacząca różnica w wydajności?
Demontaż kodu:
testIf:
13FE81B10 sub rsp,48h
13FE81B14 call qword ptr [__imp_clock (13FE81128h)]
13FE81B1A mov dword ptr [start],eax
13FE81B1E mov qword ptr [i],0
13FE81B27 jmp testIf+26h (13FE81B36h)
13FE81B29 mov rax,qword ptr [i]
13FE81B2E inc rax
13FE81B31 mov qword ptr [i],rax
13FE81B36 cmp qword ptr [i],20000000h
13FE81B3F jae testIf+0C3h (13FE81BD3h)
13FE81B45 xor edx,edx
13FE81B47 mov rax,qword ptr [counter (13FE835D0h)]
13FE81B4E mov ecx,4
13FE81B53 div rax,rcx
13FE81B56 mov rax,rdx
13FE81B59 inc rax
13FE81B5C mov qword ptr [c],rax
13FE81B61 cmp qword ptr [c],1
13FE81B67 jne testIf+6Dh (13FE81B7Dh)
13FE81B69 mov rax,qword ptr [counter (13FE835D0h)]
13FE81B70 add rax,4
13FE81B74 mov qword ptr [counter (13FE835D0h)],rax
13FE81B7B jmp testIf+0BEh (13FE81BCEh)
13FE81B7D cmp qword ptr [c],2
13FE81B83 jne testIf+89h (13FE81B99h)
13FE81B85 mov rax,qword ptr [counter (13FE835D0h)]
13FE81B8C add rax,3
13FE81B90 mov qword ptr [counter (13FE835D0h)],rax
13FE81B97 jmp testIf+0BEh (13FE81BCEh)
13FE81B99 cmp qword ptr [c],3
13FE81B9F jne testIf+0A5h (13FE81BB5h)
13FE81BA1 mov rax,qword ptr [counter (13FE835D0h)]
13FE81BA8 add rax,2
13FE81BAC mov qword ptr [counter (13FE835D0h)],rax
13FE81BB3 jmp testIf+0BEh (13FE81BCEh)
13FE81BB5 cmp qword ptr [c],4
13FE81BBB jne testIf+0BEh (13FE81BCEh)
13FE81BBD mov rax,qword ptr [counter (13FE835D0h)]
13FE81BC4 inc rax
13FE81BC7 mov qword ptr [counter (13FE835D0h)],rax
13FE81BCE jmp testIf+19h (13FE81B29h)
13FE81BD3 call qword ptr [__imp_clock (13FE81128h)]
13FE81BD9 sub eax,dword ptr [start]
13FE81BDD imul eax,eax,3E8h
13FE81BE3 cdq
13FE81BE4 mov ecx,3E8h
13FE81BE9 idiv eax,ecx
13FE81BEB cdqe
13FE81BED add rsp,48h
13FE81BF1 ret
testSwitch:
13FE81C00 sub rsp,48h
13FE81C04 call qword ptr [__imp_clock (13FE81128h)]
13FE81C0A mov dword ptr [start],eax
13FE81C0E mov qword ptr [i],0
13FE81C17 jmp testSwitch+26h (13FE81C26h)
13FE81C19 mov rax,qword ptr [i]
13FE81C1E inc rax
13FE81C21 mov qword ptr [i],rax
13FE81C26 cmp qword ptr [i],20000000h
13FE81C2F jae testSwitch+0C5h (13FE81CC5h)
13FE81C35 xor edx,edx
13FE81C37 mov rax,qword ptr [counter (13FE835D0h)]
13FE81C3E mov ecx,4
13FE81C43 div rax,rcx
13FE81C46 mov rax,rdx
13FE81C49 inc rax
13FE81C4C mov qword ptr [rsp+30h],rax
13FE81C51 cmp qword ptr [rsp+30h],1
13FE81C57 je testSwitch+73h (13FE81C73h)
13FE81C59 cmp qword ptr [rsp+30h],2
13FE81C5F je testSwitch+87h (13FE81C87h)
13FE81C61 cmp qword ptr [rsp+30h],3
13FE81C67 je testSwitch+9Bh (13FE81C9Bh)
13FE81C69 cmp qword ptr [rsp+30h],4
13FE81C6F je testSwitch+0AFh (13FE81CAFh)
13FE81C71 jmp testSwitch+0C0h (13FE81CC0h)
13FE81C73 mov rax,qword ptr [counter (13FE835D0h)]
13FE81C7A add rax,4
13FE81C7E mov qword ptr [counter (13FE835D0h)],rax
13FE81C85 jmp testSwitch+0C0h (13FE81CC0h)
13FE81C87 mov rax,qword ptr [counter (13FE835D0h)]
13FE81C8E add rax,3
13FE81C92 mov qword ptr [counter (13FE835D0h)],rax
13FE81C99 jmp testSwitch+0C0h (13FE81CC0h)
13FE81C9B mov rax,qword ptr [counter (13FE835D0h)]
13FE81CA2 add rax,2
13FE81CA6 mov qword ptr [counter (13FE835D0h)],rax
13FE81CAD jmp testSwitch+0C0h (13FE81CC0h)
13FE81CAF mov rax,qword ptr [counter (13FE835D0h)]
13FE81CB6 inc rax
13FE81CB9 mov qword ptr [counter (13FE835D0h)],rax
13FE81CC0 jmp testSwitch+19h (13FE81C19h)
13FE81CC5 call qword ptr [__imp_clock (13FE81128h)]
13FE81CCB sub eax,dword ptr [start]
13FE81CCF imul eax,eax,3E8h
13FE81CD5 cdq
13FE81CD6 mov ecx,3E8h
13FE81CDB idiv eax,ecx
13FE81CDD cdqe
13FE81CDF add rsp,48h
13FE81CE3 ret
Aktualizacja:
Ciekawe wyniki tutaj . Nie wiem, dlaczego jeden jest szybszy, a drugi wolniejszy.
12 answers
Istnieje kilka optymalizacji, które kompilator Może wykonać na przełączniku. Nie sądzę jednak, że często wspominany "jump-table" jest bardzo przydatny, ponieważ działa tylko wtedy, gdy Wejście Może być ograniczone w jakiś sposób.
C Pseudokod dla "tabeli skoków" byłby czymś w rodzaju this -- zauważ, że kompilator w praktyce musiałby wstawić jakąś formę testu if wokół tabeli, aby upewnić się, że dane wejściowe były poprawne w tabeli. Zauważ również, że działa tylko w konkretnym przypadku, że wejście jest ciągiem kolejnych liczb.
Jeśli liczba gałęzi w przełączniku jest bardzo duża, kompilator może robić rzeczy takie jak wyszukiwanie binarne na wartościach przełącznika, co (moim zdaniem) byłoby o wiele bardziej użyteczną optymalizacją, ponieważ znacznie zwiększa wydajność w niektórych scenariuszach, jest tak ogólne jak przełącznik i nie powoduje większego rozmiaru generowanego kodu. Ale aby to zobaczyć, Twój kod testowy będzie potrzebował o wiele więcej gałęzi, aby zobaczyć jakąkolwiek różnicę.
To odpowiedz na konkretne pytania:
-
Clang generuje taki, który wygląda jakto :
test_switch(char): # @test_switch(char) movl %edi, %eax cmpl $19, %edi jbe .LBB0_1 retq .LBB0_1: jmpq *.LJTI0_0(,%rax,8) jmp void call<0u>() # TAILCALL jmp void call<1u>() # TAILCALL jmp void call<2u>() # TAILCALL jmp void call<3u>() # TAILCALL jmp void call<4u>() # TAILCALL jmp void call<5u>() # TAILCALL jmp void call<6u>() # TAILCALL jmp void call<7u>() # TAILCALL jmp void call<8u>() # TAILCALL jmp void call<9u>() # TAILCALL jmp void call<10u>() # TAILCALL jmp void call<11u>() # TAILCALL jmp void call<12u>() # TAILCALL jmp void call<13u>() # TAILCALL jmp void call<14u>() # TAILCALL jmp void call<15u>() # TAILCALL jmp void call<16u>() # TAILCALL jmp void call<17u>() # TAILCALL jmp void call<18u>() # TAILCALL jmp void call<19u>() # TAILCALL .LJTI0_0: .quad .LBB0_2 .quad .LBB0_3 .quad .LBB0_4 .quad .LBB0_5 .quad .LBB0_6 .quad .LBB0_7 .quad .LBB0_8 .quad .LBB0_9 .quad .LBB0_10 .quad .LBB0_11 .quad .LBB0_12 .quad .LBB0_13 .quad .LBB0_14 .quad .LBB0_15 .quad .LBB0_16 .quad .LBB0_17 .quad .LBB0_18 .quad .LBB0_19 .quad .LBB0_20 .quad .LBB0_21
-
Mogę powiedzieć, że nie używa tabeli skoków-4 instrukcje porównania są wyraźnie widoczne:
13FE81C51 cmp qword ptr [rsp+30h],1 13FE81C57 je testSwitch+73h (13FE81C73h) 13FE81C59 cmp qword ptr [rsp+30h],2 13FE81C5F je testSwitch+87h (13FE81C87h) 13FE81C61 cmp qword ptr [rsp+30h],3 13FE81C67 je testSwitch+9Bh (13FE81C9Bh) 13FE81C69 cmp qword ptr [rsp+30h],4 13FE81C6F je testSwitch+0AFh (13FE81CAFh)
Rozwiązanie oparte na tabeli skoków w ogóle nie używa porównania.
- albo jest za mało gałęzi, aby kompilator wygenerował tabelę przeskoków, albo twój kompilator po prostu ich nie generuje. Nie jestem pewien. które.
EDIT 2014: w innych miejscach ludzie zaznajomieni z OPTIMIZEREM LLVM mówili, że optymalizacja tabeli skoków może być ważna w wielu scenariuszach; np. w przypadkach, gdy istnieje wyliczenie z wieloma wartościami i wiele przypadków przeciwko wartościom w wymienionym wyliczeniu. To powiedziawszy, trzymam się tego, co powiedziałem powyżej w 2011 roku. zbyt często widzę ludzi myślących: "jeśli zmienię to będzie to samo, bez względu na to, ile spraw mam" - i to jest całkowicie fałszywe. Nawet z tabelą skoków dostajesz pośredni koszt skoku i płacisz za wpisy w tabeli dla każdego przypadku; a przepustowość pamięci to duża sprawa na nowoczesnym sprzęcie.
Napisz kod dla czytelności. każdy kompilator wart swojej soli zobaczy drabinkę if / else if i przekształci ją w równoważny przełącznik lub odwrotnie, jeśli byłoby to szybsze.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-02 00:34:52
Na twoje pytanie:
1.Jak wyglądałaby podstawowa tabela skoków w x86 czy x64?
Jump table to adres pamięci, który zawiera wskaźnik do etykiet w strukturze tablicy. poniższy przykład pomoże Ci zrozumieć, jak są ułożone tabele skoków
00B14538 D8 09 AB 00 D8 09 AB 00 D8 09 AB 00 D8 09 AB 00 Ø.«.Ø.«.Ø.«.Ø.«.
00B14548 D8 09 AB 00 D8 09 AB 00 D8 09 AB 00 00 00 00 00 Ø.«.Ø.«.Ø.«.....
00B14558 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00B14568 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Gdzie 00b14538 jest wskaźnikiem do tabeli skoków , a wartość d8 09 AB 00 reprezentuje wskaźnik etykiety.
2.Is ten kod za pomocą skoku stolik? W tym przypadku nie.
3.Dlaczego w tym przykładzie nie ma różnicy w wydajności?
Nie ma różnicy w wydajności, ponieważ instrukcja dla obu przypadków wygląda tak samo, nie ma tabeli skoków.
4.Is czy jest jakaś sytuacja, w której istnieje znacząca różnica w wydajności?
Jeśli masz bardzo długą sekwencję Jeśli Sprawdź, w takim przypadku użycie tabeli skoków poprawia wydajność (instrukcje rozgałęzienia / jmp sądrogie jeśli nie przewidywać prawie idealnie), ale wiąże się to z kosztem pamięci.
Kod dla wszystkich instrukcji compare ma również pewien rozmiar, więc szczególnie w przypadku 32-bitowych wskaźników lub offsetów, wyszukiwanie pojedynczej tabeli skoków może nie kosztować dużo większego rozmiaru w pliku wykonywalnym.
Wniosek: kompilator jest na tyle inteligentny, że radzi sobie z takim przypadkiem i generuje odpowiednie instrukcje:)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-05-23 19:12:54
Kompilator może swobodnie kompilować instrukcję switch jako kod, który jest odpowiednikiem instrukcji if, lub tworzyć tabelę skoków. Prawdopodobnie wybierze jeden na drugim na podstawie tego, co wykona najszybszy lub wygeneruje najmniejszy kod w zależności od tego, co podałeś w opcjach kompilatora - więc w najgorszym przypadku będzie to ta sama prędkość, jak gdyby-statements
Zaufałbym kompilatorowi, który zrobi najlepszy wybór i skupi się na tym, co sprawia, że kod jest najbardziej czytelny.
Jeśli liczba przypadków staje się bardzo duża tabela skoków będzie znacznie szybsza niż seria if. Jeśli jednak kroki pomiędzy wartościami są bardzo duże, wtedy tabela skoków może stać się duża, a kompilator może wybrać, aby jej nie generować.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-24 05:14:47
Skąd wiesz, że Twój komputer nie wykonywał zadania niezwiązanego z testem podczas pętli testowej przełącznika i wykonywał mniej zadań podczas pętli testowej if? Twoje wyniki testu nie pokazują niczego jako:
- różnica jest bardzo mała
- jest tylko jeden wynik, a nie seria wyników
- jest zbyt mało przypadków
Moje wyniki:
Dodałem:
printf("counter: %u\n", counter);
Do końca, aby nie optymalizować pętli, ponieważ licznik nigdy użyte w twoim przykładzie więc dlaczego kompilator miałby wykonywać pętlę? Od razu przełącznik zawsze wygrywał nawet z takim mikro-benchmarkiem.
Inny problem z Twoim kodem to:
switch (counter % 4 + 1)
In your switch loop, versus
const size_t c = counter % 4 + 1;
W pętli if. Bardzo duża różnica, jeśli to naprawisz. Wierzę, że umieszczenie instrukcji wewnątrz instrukcji switch prowokuje kompilator do wysyłania wartości bezpośrednio do rejestrów procesora, a nie umieszczania jej najpierw na stosie. To jest zatem zwolennikiem instrukcji switch, a nie testu zbalansowanego.
I myślę, że powinieneś też zresetować licznik między testami. W rzeczywistości prawdopodobnie powinieneś używać jakiejś losowej liczby zamiast +1, +2, +3 itp., ponieważ prawdopodobnie coś tam zoptymalizuje. Przez liczbę losową mam na myśli liczbę opartą na bieżącym czasie, na przykład. W przeciwnym razie kompilator mógłby zamienić obie funkcje w jedną długą operację matematyczną i nawet nie przejmować się żadnymi pętlami.Mam zmodyfikował Kod Ryana na tyle, aby upewnić się, że kompilator nie może tego rozgryźć przed uruchomieniem kodu:
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#define MAX_COUNT (1 << 26)
size_t counter = 0;
long long testSwitch()
{
clock_t start = clock();
size_t i;
for (i = 0; i < MAX_COUNT; i++)
{
const size_t c = rand() % 20 + 1;
switch (c)
{
case 1: counter += 20; break;
case 2: counter += 33; break;
case 3: counter += 62; break;
case 4: counter += 15; break;
case 5: counter += 416; break;
case 6: counter += 3545; break;
case 7: counter += 23; break;
case 8: counter += 81; break;
case 9: counter += 256; break;
case 10: counter += 15865; break;
case 11: counter += 3234; break;
case 12: counter += 22345; break;
case 13: counter += 1242; break;
case 14: counter += 12341; break;
case 15: counter += 41; break;
case 16: counter += 34321; break;
case 17: counter += 232; break;
case 18: counter += 144231; break;
case 19: counter += 32; break;
case 20: counter += 1231; break;
}
}
return 1000 * (long long)(clock() - start) / CLOCKS_PER_SEC;
}
long long testIf()
{
clock_t start = clock();
size_t i;
for (i = 0; i < MAX_COUNT; i++)
{
const size_t c = rand() % 20 + 1;
if (c == 1) { counter += 20; }
else if (c == 2) { counter += 33; }
else if (c == 3) { counter += 62; }
else if (c == 4) { counter += 15; }
else if (c == 5) { counter += 416; }
else if (c == 6) { counter += 3545; }
else if (c == 7) { counter += 23; }
else if (c == 8) { counter += 81; }
else if (c == 9) { counter += 256; }
else if (c == 10) { counter += 15865; }
else if (c == 11) { counter += 3234; }
else if (c == 12) { counter += 22345; }
else if (c == 13) { counter += 1242; }
else if (c == 14) { counter += 12341; }
else if (c == 15) { counter += 41; }
else if (c == 16) { counter += 34321; }
else if (c == 17) { counter += 232; }
else if (c == 18) { counter += 144231; }
else if (c == 19) { counter += 32; }
else if (c == 20) { counter += 1231; }
}
return 1000 * (long long)(clock() - start) / CLOCKS_PER_SEC;
}
int main()
{
srand(time(NULL));
printf("Starting...\n");
printf("Switch statement: %lld ms\n", testSwitch()); fflush(stdout);
printf("counter: %d\n", counter);
counter = 0;
srand(time(NULL));
printf("If statement: %lld ms\n", testIf()); fflush(stdout);
printf("counter: %d\n", counter);
}
Switch: 3740
if: 3980
(podobne wyniki przy wielu próbach)
Zmniejszyłem również liczbę spraw / ifs do 5, a funkcja przełącznika nadal wygrała.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-25 03:16:15
Dobry kompilator optymalizujący taki jak MSVC może wygenerować:
- prosta tabela skoków, jeśli przypadki są ułożone w ładnym długim przedziale
- nieliczna (dwupoziomowa) tabela skoków, jeśli jest wiele luk
- szereg ifs, jeśli liczba przypadków jest mała lub wartości są Nie blisko siebie
- kombinacja powyższych przypadków, jeśli przypadki reprezentują kilka grup ściśle rozmieszczone zakresy.
W skrócie, jeśli przełącznik wygląda na wolniejszy niż szereg ifs, kompilator może po prostu przekonwertować go na jeden. I prawdopodobnie nie będzie to tylko sekwencja porównań dla każdego przypadku, ale binarne drzewo wyszukiwania. Zobacz tutaj dla przykładu.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-25 07:59:21
Odpowiem 2) i przedstawię kilka ogólnych uwag. 2) Nie, Nie ma tabeli Skoku w kodzie montażu, który napisałeś. Tabela skoków to tabela miejsc skoków i jedna lub dwie instrukcje, aby przejść bezpośrednio do zindeksowanej lokalizacji z tabeli. Tabela skoku ma większy sens, gdy istnieje wiele możliwych miejsc przełączania. Może optymalizator wie, że proste, jeśli logika else jest szybsza, chyba że liczba miejsc jest większa niż jakiś próg. Spróbuj jeszcze raz swojego przykładu za pomocą say 20 możliwości zamiast 4.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-24 05:12:39
Byłem zaintrygowany i przyjrzałem się, co mogę zmienić w twoim przykładzie, aby przyspieszyć uruchamianie instrukcji switch.
Jeśli osiągniesz 40 poleceń if i dodasz 0 case, to blok if będzie działał wolniej niż równoważna Instrukcja switch. Wyniki mam tutaj: https://www.ideone.com/KZeCz .
Efekt usunięcia przypadku 0 można zobaczyć tutaj: https://www.ideone.com/LFnrX .
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-24 16:31:10
Oto wyniki starego (teraz trudnego do znalezienia) bench++ benchmark:
Test Name: F000003 Class Name: Style
CPU Time: 0.781 nanoseconds plus or minus 0.0715
Wall/CPU: 1.00 ratio. Iteration Count: 1677721600
Test Description:
Time to test a global using a 2-way if/else if statement
compare this test with F000004
Test Name: F000004 Class Name: Style
CPU Time: 1.53 nanoseconds plus or minus 0.0767
Wall/CPU: 1.00 ratio. Iteration Count: 1677721600
Test Description:
Time to test a global using a 2-way switch statement
compare this test with F000003
Test Name: F000005 Class Name: Style
CPU Time: 7.70 nanoseconds plus or minus 0.385
Wall/CPU: 1.00 ratio. Iteration Count: 1677721600
Test Description:
Time to test a global using a 10-way if/else if statement
compare this test with F000006
Test Name: F000006 Class Name: Style
CPU Time: 2.00 nanoseconds plus or minus 0.0999
Wall/CPU: 1.00 ratio. Iteration Count: 1677721600
Test Description:
Time to test a global using a 10-way switch statement
compare this test with F000005
Test Name: F000007 Class Name: Style
CPU Time: 3.41 nanoseconds plus or minus 0.171
Wall/CPU: 1.00 ratio. Iteration Count: 1677721600
Test Description:
Time to test a global using a 10-way sparse switch statement
compare this test with F000005 and F000006
Widzimy z tego, że (na tej maszynie, z tym kompilatorem -- VC++ 9.0 x64), każdy if
test trwa około 0,7 nanosekundy. Wraz ze wzrostem liczby testów czas skaluje się niemal idealnie liniowo.
Z instrukcją switch, nie ma prawie żadnej różnicy prędkości między testem 2-drożnym a 10-drożnym, o ile wartości są gęste. Test 10-drożny z rzadkim wartości zajmuje około 1,6 x tyle czasu, co test 10-drożny z gęstymi wartościami-ale nawet z rzadkimi wartościami, nadal lepiej niż dwa razy szybciej niż 10-drożny if
/else if
.
Reasumując: Korzystanie tylko z testu 4-way nie pokaże ci wiele {[16] } o wydajności switch
vs if
/else
. Jeśli spojrzymy na liczby z tego kodu, dość łatwo jest interpolować fakt, że w przypadku testu 4-drożnego spodziewamy się, że te dwa wyniki będą podobne (~2,8 nanosekundy dla testu 4-drożnego). if
/else
, ~2.0 dla switch
).
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-26 03:20:54
Zauważ, że gdy przełącznik nie jest skompilowany do tabeli skoków, bardzo często można napisać if ' S wydajniej niż przełącznik...
(1) jeśli przypadki mają kolejność, a nie najgorszy przypadek testowania dla wszystkich N, można napisać if ' S do testowania if w górnej lub dolnej połowie, a następnie w każdej połowie tego, Binarny Styl wyszukiwania... w wyniku czego w najgorszym przypadku logN zamiast N
(2) jeśli niektóre przypadki / grupy są znacznie częstsze niż inne przypadki, to projektowanie if ' S to odizoluj te przypadki najpierw może przyspieszyć średni czas przez
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-01 09:22:10
Nie są to if then jump else if then jump else...Tabela skoków ma tabelę adresów lub używa hasha lub czegoś takiego.
Szybszy czy wolniejszy jest subiektywny. Na przykład case 1 może być ostatnią rzeczą zamiast pierwszej i jeśli twój program testowy lub program w świecie rzeczywistym używał case 1 przez większość czasu, kod byłby wolniejszy z tą implementacją. Tak więc samo ponowne ułożenie listy spraw, w zależności od implementacji, może zrobić dużą różnicę.
Jeśli gdyby użyto przypadków 0-3 zamiast 1-4 kompilator mógłby użyć tabeli skoków, kompilator i tak powinien się zorientować, że usuwa twój +1. Być może była to niewielka liczba przedmiotów. Gdybyś zrobił to 0-15 lub 0 - 31 na przykład może zaimplementować go z tabelą lub użyć innego skrótu. Kompilator ma swobodę wyboru sposobu implementacji, o ile spełnia funkcje kodu źródłowego. I to dostaje się do różnic kompilatora i różnice wersji i optymalizacji różnice. Jeśli chcesz tabelę skoków, zrób tabelę skoków, jeśli chcesz drzewo if-then-else zrób drzewo if-then-else. Jeśli chcesz, aby kompilator zdecydował, użyj instrukcji switch/case.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-24 14:00:07
Nie wiem, dlaczego jeden jest szybszy, a drugi wolniejszy.To nie jest trudne do wyjaśnienia... Jeśli pamiętacie, że źle określone gałęzie są dziesiątki do setek razy droższe niż prawidłowo przewidywane gałęzie.
W wersji % 20
pierwszy przypadek/if jest zawsze tym, który uderza. Nowoczesne procesory "uczą się", które gałęzie są zwykle brane, a które nie, dzięki czemu mogą łatwo przewidzieć, jak ta gałąź będzie się zachowywać na prawie każdej iteracji pętla. To wyjaśnia, dlaczego wersja" if " leci; nigdy nie musi wykonywać niczego poza pierwszym testem i (poprawnie) przewiduje wynik tego testu dla większości iteracji. Oczywiście" przełącznik " jest zaimplementowany nieco inaczej - być może nawet tabela skoków, która może być powolna dzięki obliczonej gałęzi.
W wersji % 21
gałęzie są zasadniczo losowe. Więc nie tylko wielu z nich wykonuje każdą iterację, ale procesor nie może odgadnąć, w którą stronę pójdą. To jest to przypadek, w którym tabela skoku (lub inna optymalizacja "przełącznika") może pomóc.
Bardzo trudno jest przewidzieć, jak kawałek kodu będzie działał z nowoczesnym kompilatorem i procesorem, a z każdą generacją jest to coraz trudniejsze. Najlepszą radą jest "nawet nie próbuj, zawsze profiluj". Ta rada staje się lepsza , a liczba ludzi, którzy mogą ją z powodzeniem ignorować, zmniejsza się z każdym rokiem.
To wszystko oznacza, że moje wyjaśnienie powyżej jest w dużej mierze domysłem. :-)Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-25 02:33:16
Brak. W większości przypadków, gdy idziesz do asemblera i wykonujesz rzeczywiste pomiary wydajności, twoje pytanie jest po prostu złe. Dla podanego przykładu twoje myślenie jest zdecydowanie zbyt krótkie, ponieważ
counter += (4 - counter % 4);
Wygląda mi na poprawne wyrażenie przyrostowe, którego powinieneś używać.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-24 07:55:54