Если против скорости переключения

Операторы Switch обычно быстрее, чем эквивалентные операторы if-else-if (например, descibed в этой статье) из-за оптимизации компилятора.

Как эта оптимизация действительно работает? Кто-нибудь имеет хорошее объяснение?

Ответ 1

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

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

Ответ 2

Конрад является правильным. В случае включения непрерывных диапазонов целых чисел (например, где у вас есть случай 0, случай 1, случай 2.. случай n), компилятор может сделать что-то еще лучше, потому что ему даже не нужно создавать хеш-таблицу; он просто хранит массив указателей на функции и, таким образом, может загружать свою цель перехода в постоянное время.

Ответ 3

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

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

switch(a) { case 0: ...; break; case 1: ...; break; }

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

Ответ 4

Как сказал Конрад, компилятор может построить таблицу Jump.

В С++ причина, по которой это возможно, связана с ограничением коммутаторов.

Ответ 5

Операторы switch/case обычно могут быть быстрее на 1 уровне, но когда вы начинаете получать 2 или более, инструкции switch/case начинаются в 2-3 раза до тех пор, пока вложенные операторы if/else.

В этой статье приведены некоторые сравнения скорости, подчеркивающие различия в скорости, когда такие заявления вложены.

Например, согласно их тестам, пример кода выглядит следующим образом:

if (x % 3 == 0)
            if (y % 3 == 0)
                total += 3;
            else if (y % 3 == 1)
                total += 2;
            else if (y % 3 == 2)
                total += 1;
            else
                total += 0;
        else if (x % 3 == 1)
            if (y % 3 == 0)
                total += 3;
            else if (y % 3 == 1)
                total += 2;
            else if (y % 3 == 2)
                total += 1;
            else
                total += 0;
        else if (x % 3 == 2)
            if (y % 3 == 0)
                total += 3;
            else if (y % 3 == 1)
                total += 2;
            else if (y % 3 == 2)
                total += 1;
            else
                total += 0;
        else
            if (y % 3 == 0)
                total += 3;
            else if (y % 3 == 1)
                total += 2;
            else if (y % 3 == 2)
                total += 1;
            else
                total += 0;

закончилось за половину времени, которое потребовалось для выполнения эквивалентного оператора switch/case:

switch (x % 3)
    {
        case 0:
            switch (y % 3)
            {
                case 0: total += 3;
                    break;
                case 1: total += 2;
                    break;
                case 2: total += 1;
                    break;
                default: total += 0;
                    break;
            }
            break;
        case 1:
            switch (y % 3)
            {
                case 0: total += 3;
                    break;
                case 1: total += 2;
                    break;
                case 2: total += 1;
                    break;
                default: total += 0;
                    break;
            }
            break;
    case 2:
            switch (y % 3)
            {
                case 0: total += 3;
                    break;
                case 1: total += 2;
                    break;
                case 2: total += 1;
                    break;
                default: total += 0;
                    break;
            }
            break;
    default:
        switch (y % 3)
        {
            case 0: total += 3;
                break;
            case 1: total += 2;
                break;
            case 2: total += 1;
                break;
            default: total += 0;
                break;
        }
        break;
    }

Да, это рудиментарный пример, но он иллюстрирует суть.

Таким образом, вывод может заключаться в использовании switch/case для простых типов, которые только на одном уровне, но для более сложных сравнений и множественных вложенных уровней используются классические конструкции if/else?

Ответ 6

Статистика без соответствия не может быть хорошей.

Если вы действительно загружаете источник, значения совпадения, как известно, равны 21, как в случае if, так и в switch. Компилятор должен уметь абстрагироваться, зная, какой оператор должен выполняться в любое время, а CPU должен иметь возможность правильно предсказать ветку.

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

Ответ 7

Это код для микроконтроллера PIC18 на языке C:

void main() {
int s1='0';
int d0;
int d1;
//if (s1 == '0') {d1 = '0'; d0 = '0';}
//else if (s1 == '1') {d1 = '0';d0 = '1';}
//else if (s1 == '2') {d1 = '1';d0 = '0';}
//else if (s1 == '3') {d1 = '1';d0 = '1';}
switch (s1) {
      case  '0': {d1 = '0';d0 = '0';} break;
      case  '1': {d1 = '0';d0 = '1';} break;
      case  '2': {d1 = '1';d0 = '0';} break;
      case  '3': {d1 = '1';d0 = '1';} break;
    }
}

С ifs

s1='0' - 14 cycles
s1='1' - 21 cycles
s1='2' - 28 cycles
s1='3' - 33 cycles
s1='4' - 34 cycles

С помощью случаев

s1='0' - 17 cycles
s2='1' - 23 cycles
s3='2' - 29 cycles
s4='3' - 35 cycles
s5='4' - 32 cycles

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