Является ли "переключатель" быстрее, чем "если"?

Оператор switch самом деле быстрее, чем оператор if?

Я запустил приведенный ниже код на компиляторе Visual Studio 2010 x64 C++ с флагом /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());
}

и получил эти результаты:

Переключатель: 5261 мс
Если утверждение: 5196 мс

Из того, что я узнал, операторы switch явно используют таблицы 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       

Обновить:

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

Ответы

Ответ 1

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

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

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

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

  • Clang генерирует тот, который выглядит как this:

    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
    
  • Я могу сказать, что он не использует таблицу переходов - четкие инструкции сравнения хорошо видны:

    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) 
    

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

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

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

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

Ответ 2

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

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. Есть ли ситуации, в которых существует значительная разница в производительности?

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

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

Вывод: компилятор достаточно умен, обрабатывает такие случаи и генерирует соответствующие инструкции :)

Ответ 3

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

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

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

Ответ 4

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

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

Мои результаты:

Я добавил:

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

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

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

switch (counter % 4 + 1)

в вашем цикле переключения, по сравнению с

const size_t c = counter % 4 + 1; 

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

О, и я думаю, что вы должны также reset counter между тестами. Фактически, вы, вероятно, должны использовать какое-то случайное число вместо +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, и функция переключения все еще выиграла.

Ответ 5

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

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

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

Ответ 6

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

Ответ 7

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

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

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

Ответ 8

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

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

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

Ответ 9

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

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

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

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

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

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

Ответ 10

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

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

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

Ответ 11

Вот некоторые результаты из теста старого (теперь трудно найти) bench ++:

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

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

С помощью оператора switch практически нет разницы в скорости между двухсторонним и 10-позиционным тестами, если значения плотны. 10-позиционный тест с разреженными значениями занимает примерно в 1,6 раза больше, чем 10-позиционный тест с плотными значениями, но даже с разреженными значениями, еще лучше, чем в два раза быстрее, чем 10-way if/else if.

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

Ответ 12

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

counter += (4 - counter % 4);

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