На самом низком уровне (в оборудовании) да, если они дороги. Чтобы понять почему, вы должны понимать, как работают конвейеры .
Текущая команда, которая должна быть выполнена, хранится в том, что обычно называется указателем команд (IP) или счетчиком программ (ПК); эти термины синонимичны, но для разных архитектур используются разные термины. Для большинства инструкций ПК следующей инструкции - это просто текущий ПК плюс длина текущей инструкции. Для большинства архитектур RISC все инструкции имеют постоянную длину, поэтому ПК можно увеличивать на постоянную величину. Для архитектур CISC, таких как x86, инструкции могут иметь переменную длину, поэтому логика, которая декодирует инструкцию, должна определять, как долго текущая инструкция должна найти местоположение следующей инструкции.
Однако для инструкций ветвления следующая инструкция, которая должна быть выполнена, не находится в следующей позиции после текущей инструкции. Ветви - это gotos - они сообщают процессору, где находится следующая инструкция. Ветви могут быть условными или безусловными, а целевое местоположение может быть фиксированным или вычисленным.
Условное и безусловное легко понять - условная ветвь берется только в том случае, если выполняется определенное условие (например, равно ли одно число другому); если ветвление не выполнено, управление переходит к следующей инструкции после ветвления, как обычно. Для безусловных переходов всегда берется ветвь. Условные переходы появляются в if
операторах и контрольных тестахfor
и while
петель. Безусловные переходы проявляются в бесконечных циклах, вызовах функций, возвратах функций break
и continue
операторах, печально известном goto
утверждении и многом другом (эти списки далеко не исчерпывающие).
Целевая ветвь - еще одна важная проблема. Большинство веток имеют фиксированную цель ветвления - они переходят в определенное место в коде, которое фиксируется во время компиляции. Сюда входят if
операторы, циклы всех видов, регулярные вызовы функций и многое другое. Вычисляемые ветви вычисляют цель ветви во время выполнения. Сюда входят switch
операторы (иногда), возврат из функции, вызовы виртуальных функций и вызовы указателей функций.
Что все это значит для производительности? Когда процессор видит, что в его конвейере появляется инструкция ветвления, ему необходимо выяснить, как продолжить заполнение своего конвейера. Чтобы выяснить, какие инструкции идут после ветки в потоке программы, ему необходимо знать две вещи: (1) будет ли ветвь взята и (2) цель ветки. Это называется предсказанием ветвления , и это сложная задача. Если процессор угадает правильно, программа продолжает работу на полной скорости. Если вместо того, чтобы процессор угадывает неправильно , он просто провел некоторое время вычисления неправильных вещей. Теперь он должен очистить свой конвейер и перезагрузить его инструкциями из правильного пути выполнения. Итог: большая производительность.
Таким образом, причина, по которой операторы if являются дорогостоящими, связана с неверными предсказаниями ветвлений . Это только на самом низком уровне. Если вы пишете код высокого уровня, вам совсем не нужно беспокоиться об этих деталях. Об этом следует заботиться только в том случае, если вы пишете крайне критичный для производительности код на C или ассемблере. В этом случае написание кода без ветвлений часто может быть лучше, чем код с ветвлениями, даже если требуется еще несколько инструкций. Есть некоторые интересные битовые-вертел трюки , которые вы можете сделать , чтобы вычислить такие вещи, как abs()
, min()
и max()
без ветвления.