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


112

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

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



Возможный хороший ответ: dotnetperls.com/if-switch-performance
Бабак

Ответы:


185

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

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


6
Хороший ответ, интересно про хеш-таблицу.
BobbyShaftoe

4
В некоторых случаях они также преобразуются в сравнения деревьев. Рассуждения несколько сложны, но в основном сводятся к косвенному обращению к таблице, стерилизующему современные целевые буферы переходов процессора и таким образом уничтожающему предсказатель ветвления. Я смутно припоминаю доклад на конференции GCC о генерации кода для коммутаторов.
olliej

Это означает: switch (a) case «x»: case «y»: case «z»: // что-то сломается; } быстрее, чем: if (a == "x" || a == "b" || a == "c") // что-то верно?
yazanpro

здесь у нас нет вложенных if else, только ИЛИ, так что вы думаете?
yazanpro

@yazanpro На старых компиляторах потенциально да (но учтите, что количество случаев настолько мало, что это может не иметь значения!). Однако современные компиляторы выполняют гораздо больше анализа кода. Как следствие, они могут выяснить, что эти два фрагмента кода эквивалентны, и применить одинаковые оптимизации. Но это чистое предположение с моей стороны, я не знаю, действительно ли это делает какой-либо компилятор.
Конрад Рудольф

15

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

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

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

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


4

Статистика отсутствия матчей может быть плохой.

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

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


4

Операторы switch / case обычно могут быть быстрее на один уровень, но когда вы начинаете переходить на 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;
    }

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

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


-1: 1. В статье полностью игнорируется прогноз ветвления, 2. алгоритмы не совсем такие же (единственный if-else в ссылке уже закодирован более оптимизированным) и 3. обнаруженные различия настолько малы, что ничто не оправдывает использование правильного, чистого кода (около 4 нс на 10
000

Этот пример не будет оптимизирован из-за того, что у блока переключателя мало случаев. Обычно после 5-6 элементов создается таблица переходов.
antiduh

0

Единственное преимущество случая if over - это когда наблюдается заметное увеличение частоты появления первого случая.

Не уверен, где именно находится порог, но я использую синтаксис case, если только первое «почти всегда» не проходит первый тест.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.