Операторы switch обычно быстрее, чем эквивалентные операторы if-else-if (например, описанные в этой статье ) из-за оптимизации компилятора.
Как на самом деле работает эта оптимизация? У кого-нибудь есть хорошее объяснение?
Операторы switch обычно быстрее, чем эквивалентные операторы if-else-if (например, описанные в этой статье ) из-за оптимизации компилятора.
Как на самом деле работает эта оптимизация? У кого-нибудь есть хорошее объяснение?
Ответы:
Компилятор может создавать таблицы переходов, где это применимо. Например, когда вы используете отражатель для просмотра созданного кода, вы увидите, что для огромных переключателей строк компилятор фактически сгенерирует код, который использует хеш-таблицу для их отправки. Хеш-таблица использует строки как ключи и делегирует case
коды как значения.
У этого асимптотического лучшего времени выполнения, чем у множества связанных if
тестов, и на самом деле он быстрее даже для относительно небольшого количества строк.
Это небольшое упрощение, поскольку обычно любой современный компилятор встречает if..else if ..
последовательность, которая может быть тривиально преобразована в оператор switch, компилятор тоже. Но просто для того, чтобы добавить дополнительного удовольствия, компилятор не ограничен синтаксисом, поэтому он может генерировать внутри себя «switch» подобные операторы, которые имеют сочетание диапазонов, одиночных целей и т.д. - и они могут (и делают) это как для switch, так и для if. .else заявления.
Как бы то ни было, расширение ответа Конрада состоит в том, что компилятор может генерировать таблицу переходов, но это не обязательно гарантировано (и не желательно). По ряду причин таблицы переходов плохо влияют на предсказатели ветвления на современных процессорах, а сами таблицы плохо влияют на поведение кеша, например.
switch(a) { case 0: ...; break; case 1: ...; break; }
Если бы компилятор действительно сгенерировал для этого таблицу переходов, он, вероятно, был бы медленнее, чем if..else if..
код альтернативного стиля, поскольку таблица переходов не позволяла предсказывать переход.
Статистика отсутствия матчей может быть плохой.
Если вы действительно загружаете исходный код, известно, что значение отсутствия совпадения равно 21, как в случае if, так и в случае переключения. Компилятор должен иметь возможность абстрагироваться, зная, какой оператор должен выполняться в любое время, а ЦП должен иметь возможность правильно предсказывать ветвление.
На мой взгляд, более интересный случай - это когда не каждый случай дает сбой, но это могло не входить в рамки эксперимента.
Операторы 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?