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:

  1. Jaka byłaby podstawowa tabela skoków wygląda jak w x86 czy x64?

  2. Czy ten kod używa tabeli skoków?

  3. 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.

Author: Community, 2011-07-24

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:

  1. 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
    
  2. 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.

  3. 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.

 124
Author: Billy ONeal,
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  ................

Tutaj wpisz opis obrazka

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:)

 48
Author: crypted,
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ć.

 31
Author: Soren,
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:

  1. różnica jest bardzo mała
  2. jest tylko jeden wynik, a nie seria wyników
  3. 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.

 13
Author: BobTurbo,
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ć:

  1. prosta tabela skoków, jeśli przypadki są ułożone w ładnym długim przedziale
  2. nieliczna (dwupoziomowa) tabela skoków, jeśli jest wiele luk
  3. szereg ifs, jeśli liczba przypadków jest mała lub wartości są Nie blisko siebie
  4. 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.

 7
Author: Igor Skochinsky,
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.

 5
Author: Bill Forster,
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 .

 4
Author: Ryan Gross,
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).

 4
Author: Jerry Coffin,
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

 3
Author: Brian Kennedy,
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.

 2
Author: old_timer,
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. :-)
 2
Author: Nemo,
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ć.

 1
Author: Jens Gustedt,
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