"Switch" быстрее, чем "if"?

это switch сообщении на самом деле быстрее if заявление?

я запустил приведенный ниже код в компиляторе x64 C++ Visual Studio 2010 с помощью /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 msn", testSwitch());
    printf("If     statement: %u msn", testIf());
}

и получил следующие результаты:

переключатель заявление: 5261 ms
Оператор If: 5196 ms

из того, что я узнал, switch операторы, по-видимому, используют таблицы переходов для оптимизации ветвление.

вопросы:

  1. как будет выглядеть базовая таблица переходов в x86 или x64?

  2. этот код использует таблицу перехода?

  3. почему в этом примере нет разницы в производительности? Есть ли какая-либо ситуация, в которой и существенная разница в производительности?


разборки код:

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       

обновление:

интересные результаты здесь. Не уверен, почему один быстрее,а другой медленнее.

12 ответов


существует несколько оптимизаций компилятора can сделайте на переключателе. Я не думаю, что часто упоминаемый "jump-table" очень полезен, поскольку он работает только тогда, когда вход может быть ограничен каким-то образом.

C псевдокод для "таблицы перехода" будет чем-то вроде этой -- обратите внимание, что компилятору на практике нужно будет вставить некоторую форму теста if вокруг таблицы, чтобы убедиться, что входные данные были действительны в таблице. Отметим также, что он работает только в конкретный случай, когда вход представляет собой последовательность последовательных чисел.

Если количество ветвей в коммутаторе чрезвычайно велико, компилятор может делать такие вещи, как использование двоичного поиска по значениям коммутатора, что (на мой взгляд) было бы гораздо более полезной оптимизацией, поскольку это значительно увеличивает производительность в некоторых сценариях, является таким же общим, как коммутатор, и не приводит к большему размеру генерируемого кода. Но чтобы увидеть это, вашему тестовому коду потребуется гораздо больше ветвей, чтобы увидеть любые разница.

чтобы ответить на ваши конкретные вопросы:

  1. Clang генерирует тот, который выглядит как этой:

    test_switch(char):                       # @test_switch(char)
            movl    %edi, %eax
            cmpl    , %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. Я могу сказать, что он не использует таблицу перехода -- 4 Инструкции по сравнению четко видны:

    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) 
    

    решение на основе таблицы переходов не использует сравнение вообще.

  3. либо недостаточно ветвей, чтобы компилятор генерировал таблицу переходов, либо компилятор просто не генерирует их. Я не уверен, что.

редактировать: в другом месте люди, знакомые с оптимизатором LLVM, говорили, что оптимизация таблицы переходов может быть важна во многих сценариях; например, в случаях, когда есть перечисление со многими значениями и многими случаями против значений в указанном перечислении. Тем не менее, я поддерживаю то, что я сказал выше в 2011 году-слишком часто я вижу, как люди думают: "если я сделаю это, это будет одно и то же время, независимо от того, сколько у меня дел" - и это совершенно ложно. Даже с таблицей перехода вы получаете косвенную стоимость перехода и платите за записи в таблице для каждого случая; а пропускная способность памяти-большое дело на современном оборудовании.

написать код для удобочитаемости. любой компилятор, стоящий своей соли, увидит лестницу if / else if и преобразует ее в эквивалентный коммутатор или наоборот, если это будет быстрее.


на ваш вопрос:

1.Как будет выглядеть базовая таблица переходов в x86 или x64?

таблица переходов-это адрес памяти, который содержит указатель на метки в чем-то вроде структуры массива. следующий пример поможет вам понять, как выглядит таблица переходов

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

enter image description here

здесь 00B14538 - это указатель на таблицу переходов , и значение D8 09 AB 00 представляет указатель метки.

2.Этот код использует таблицу перехода? В данном случае-нет.

3.Почему в этом примере нет разницы в производительности?

нет разницы в производительности, потому что инструкция для обоих случаев выглядит одинаково, нет таблицы перехода.

4.Существует ли какая-либо ситуация, в которой существует значительная разница в производительности?

Если у вас очень длинные последовательности если проверить, в в этом случае использование таблицы переходов снижает производительность, но это связано со стоимостью памяти.

девиз: компилятор достаточно умен, чтобы справиться с таким случаем:)


компилятор может скомпилировать оператор switch как код, эквивалентный if-оператору, или создать таблицу переходов. Скорее всего, он выберет один на другом на основе того, что будет выполняться быстрее или генерировать наименьший код в зависимости от того, что вы указали в параметрах компилятора-так что в худшем случае это будет та же скорость, что и if-statements

Я бы доверять компилятору, чтобы сделать лучший выбор и сосредоточиться на том, что делает код читаемый.

Если количество случаев становится очень большим, таблица переходов будет намного быстрее, чем серия if. Однако если шаги между значениями очень велики, таблица переходов может стать большой, и компилятор может не создавать ее.


откуда вы знаете, что ваш компьютер не выполнял какую-то задачу, не связанную с тестом во время цикла тестирования коммутатора, и выполнял меньше задач во время цикла тестирования if? Ваши результаты теста не показывают ничего, как:

  1. разница очень мала
  2. есть только один результат, а не серия результаты
  3. слишком мало случаев

мой результат:

Я предоставлена:

printf("counter: %u\n", counter);

до конца, чтобы он не оптимизируйте цикл, поскольку счетчик никогда не использовался в вашем примере, так почему компилятор выполняет цикл? Сразу же коммутатор всегда выигрывал даже с таким микро-бенчмарком.

другая проблема с вашим кодом:

switch (counter % 4 + 1)

в цикле переключения, против

const size_t c = counter % 4 + 1; 

в вашем цикле if. Очень большая разница, если вы это исправите. Я считаю, что размещение оператора внутри оператора switch провоцирует компилятор на отправку значения напрямую в регистры CPU, а не помещать его в стек первым. Поэтому это в пользу оператора switch, а не сбалансированного теста.

О, и я думаю, что вы также должны сбросить счетчик между тестами. На самом деле, вы, вероятно, должны использовать какое-то случайное число вместо +1, +2, +3 и т. д., Поскольку это, вероятно, оптимизирует что-то там. Под случайным числом я имею в виду число, основанное на текущем времени, например. В противном случае, компилятор может превратить ваши функции в одном длинная математическая операция и даже не беспокоиться о каких-либо петлях.

Я изменил код Райана достаточно, чтобы убедиться, что компилятор не мог понять вещи до запуска кода:

#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);
} 

переключатель: 3740
если: 3980

(аналогичные результаты за несколько попыток)

Я также уменьшил количество случаев / ifs до 5, и функция переключателя все еще выиграла.


хороший оптимизирующий компилятор, такой как MSVC, может генерировать:

  1. простая таблица перехода, если случаи расположены в хорошем Дальнем диапазоне
  2. разреженная (двухуровневая) таблица прыжков, если есть много пробелов
  3. серия ifs, если число случаев невелико или значения не близко друг к другу
  4. комбинацию выше, если случаи представляют несколько групп близко расположенные хребты.

короче говоря, если переключатель выглядит медленнее, чем серия ifs, компилятор может просто преобразовать его в один. И это скорее всего будет не просто последовательность сравнений для каждого случая, но бинарное дерево поиска. См.здесь для примера.


Я отвечу 2) и сделаю несколько общих замечаний. 2) нет, в коде сборки, который вы разместили, нет таблицы перехода. Таблица переходов-это таблица назначений переходов и одна или две инструкции для перехода непосредственно в индексированное местоположение из таблицы. Таблица перехода будет иметь больше смысла, когда есть много возможных направлений переключения. Возможно, оптимизатор знает, что просто, если логика else быстрее, если количество назначений не превышает некоторого порога. Попробуйте еще раз пример с say 20 возможностей вместо 4.


Я был заинтригован и посмотрел, что я могу изменить в вашем примере, чтобы заставить его быстрее запускать оператор switch.

Если вы получаете 40 операторов if и добавляете случай 0, то блок if будет работать медленнее, чем эквивалентный оператор switch. У меня результаты вот: https://www.ideone.com/KZeCz.

эффект удаления случая 0 можно увидеть здесь:https://www.ideone.com/LFnrX.


нет, это если затем прыгать еще, если затем прыгать еще...Таблица перехода будет иметь таблицу адресов или использовать хэш или что-то в этом роде.

быстрее или медленнее субъективно. Например, случай 1 может быть последним, а не первым, и если ваша тестовая программа или программа реального мира использовали случай 1 большую часть времени, код будет медленнее с этой реализацией. Поэтому просто перестановка списка дел, в зависимости от реализации, может сделать большой разница.

Если бы вы использовали случаи 0-3 вместо 1-4, компилятор мог бы использовать таблицу перехода, компилятор должен был выяснить, удаляя ваш +1 в любом случае. Возможно, дело было в небольшом количестве предметов. Если бы вы сделали это 0-15 или 0 - 31, например, он, возможно, реализовал его с таблицей или использовал какой-то другой ярлык. Компилятор может свободно выбирать, как он реализует вещи, если он соответствует функциональности исходного кода. И это попадает в различия компилятора и различия версий и различия оптимизации. Если вам нужна таблица переходов, сделайте таблицу переходов, если вам нужно дерево "если-то-еще", сделайте дерево "если-то-еще". Если вы хотите, чтобы компилятор принял решение, используйте оператор switch/case.


не уверен, почему один быстрее,а другой медленнее.

это на самом деле не слишком сложно объяснить... Если вы помните, что неправильно интерпретированные ветви в десятки-сотни раз дороже, чем правильно предсказанные ветви.

на % 20 версия, первый случай / если всегда тот, который попадает. Современные процессоры "узнают", какие ветви обычно берутся, а какие нет, поэтому они могут легко предсказать, как эта ветвь будет вести себя почти каждая итерация цикла. Это объясняет, почему версия "if" летает; она никогда не должна выполнять что-либо после первого теста, и она (правильно) предсказывает результат этого теста для большинства итераций. Очевидно, что "переключатель" реализован немного иначе-возможно, даже таблица переходов, которая может быть медленной благодаря вычисляемой ветви.

на % 21 версия, ветви по существу случайны. Поэтому многие из них не только выполняют каждую итерацию, CPU не может угадать в какую сторону они пойдут. Это тот случай, когда таблица переходов (или другая оптимизация "коммутатора"), вероятно, поможет.

очень трудно предсказать, как часть кода будет работать с современным компилятором и процессором, и это становится все труднее с каждым поколением. Лучший совет: "даже не пытайтесь; всегда профиль". Этот совет становится лучше-и число людей, которые могут успешно игнорировать его, уменьшается-с каждым годом.

все это означает, что мой объяснение выше в значительной степени является догадкой. :-)


обратите внимание, что когда коммутатор не компилируется в таблицу переходов, вы можете очень часто писать if более эффективно, чем коммутатор...

(1) Если в случаях есть порядок, а не худший случай тестирования для всех N, вы можете написать свои if, чтобы проверить, если в верхней или нижней половине, то в каждой половине этого, двоичный стиль поиска... в результате в худшем случае logN, а не N

(2) если некоторые случаи / группы гораздо чаще, чем другие случаи, то проектирование ваше если изолировать эти случаи сначала может ускорить среднее время через


вот некоторые результаты из старого (теперь трудно найти) 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

из этого видно, что (на этой машине с этим компилятором -- VC++ 9.0 x64) каждый if тест занимает около 0.7 наносекунды. По мере увеличения числа тестов время масштабируется почти идеально линейно.

с оператором switch есть почти никакой разницы в скорости между 2-и 10-способ испытания, а также значения плотные. Этот 10-полосный тест с разреженными значениями занимает около 1,6 x столько же времени, сколько 10-полосный тест с плотными значениями, но даже с разреженными значениями, все же лучше, чем в два раза быстрее 10-полосного if/else if.

итог: использование только 4-полосного теста на самом деле не покажет вам много о работе switch vs if/else. Если вы посмотрите на цифры из этого кода, довольно легко интерполировать тот факт, что для 4-стороннего теста мы ожидаем, что они будут производить довольно аналогичные результаты (~2,8 наносекунды для if/else, ~2.0 для switch).


нет. В большинстве частных случаев, когда вы входите в ассемблер и выполняете реальные измерения производительности, ваш вопрос просто неправильный. Для данного примера ваше мышление определенно слишком короткое, так как

counter += (4 - counter % 4);

Кажется мне правильным выражением приращения, которое вы должны использовать.