Что быстрее, switch или if else?
Я пишу функцию для перемещения угла прямоугольника. Будет ли разница между этими двумя вариациями по скорости?
template<rect_corner Corner>
void move_corner(const coords2<value_type>& coords) noexcept {
if constexpr (Corner == rect_corner::left_top) {
left = coords.x;
top = coords.y;
return;
}
if constexpr (Corner == rect_corner::left_bottom) {
left = coords.x;
bottom = coords.y;
return;
}
if constexpr (Corner == rect_corner::right_top) {
right = coords.x;
top = coords.y;
return;
}
if constexpr (Corner == rect_corner::right_bottom) {
right = coords.x;
bottom = coords.y;
return;
}
}
void move_corner(const rect_corner corner, const coords2<value_type>& coords) noexcept {
switch (corner) {
case rect_corner::left_top:
left = coords.x;
top = coords.y;
break;
case rect_corner::left_bottom:
left = coords.x;
bottom = coords.y;
break;
case rect_corner::right_top:
right = coords.x;
top = coords.y;
break;
default: // corner == right_bottom.
right = coords.x;
bottom = coords.y;
break;
}
}
По идее, первый вариант выберет нужный кусок кода на этапе компиляции, а второй будет выполнять последовательное сравнение, что должно быть дольше. Я замерил скорость выполнения этих функций в цикле и не заметил разницы, но, мне кажется, что первая должна быть быстрее.
Ответы (1 шт):
Поток управления в инструкциях if-elseif-else является линейным: вычисляется условие if и, если оно истинно, выполняется первый блок. В противном случае продолжается вычисление условий каждого else if и выполнение первого же блока, условие которого имеет значение t rue. Проверка переменной на равенство для каждого из п значений приводит к последовательности if-then-elseifс п блоками. Если все возможные значения равновероятны, такая последовательность if-then-e lseif выполняет О(n) сравнений. При очень частом выполнении (например, при диспетчеризации событий) стоимость такого кода возрастает многократно.
Инструкция switch также сравнивает переменную с каждым из n значений, но оператор switch, сравнивающий значение с рядом констант, позволяет компилятору выполнять ряд полезных оптимизаций.
В часто встречающемся случае, когда требуется проверка на равенство константам, взятым из множества последовательных или почти последовательных значений, инструкция switch компилируется в таблицу переходов, индексируемую тестируемым значением или выражением, производным от него. И инструкция switch выполняет одну операцию индексации и переходит по указанному в таблице адресу. Стоимость сравнения равна 0(1), независимо от количества констант для проверки. Такие переходы в таблице не обязаны находиться в последовательном порядке; компилятор может по-своему сортировать таблицу переходов. Когда константы образуют последовательность с большими разрывами, таблица переходов становится слишком большой. Компилятор по-прежнему может отсортировать тестируемые константы и сгенерировать код, который выполняет бинарный поиск. Для инструкции switch, выполняющей сравнение с n значениями, наихудшая стоимость поиска составляет O(log2n). В любом случае компилятор никогда не сгенерирует для инструкции switch код, более медленный, чем для эквивалентной конструкции if-else.
Иногда вероятность одной ветви if-elseif-else оказывается гораздо выше прочих. В этом случае амортизированная производительность инструкции if может приближаться к константе, если первым проверяется самый вероятный случай.
Отрывок из книги "Оптимизация программ на C++. Проверенные методы повышения производительности | Курт Гантерот"