"ЕСЛИ" дорого? - PullRequest
       41

"ЕСЛИ" дорого?

81 голосов
/ 24 ноября 2008

Не могу, на всю жизнь, вспомнить, что именно сказал наш учитель в тот день, и я надеюсь, что вы, вероятно, знаете.

Модуль "Структуры данных и алгоритмы", и он сказал нам что-то вроде:

Оператор if самый дорогой [что-то]. [что-то] регистрируется [Что-то].

Да, у меня ужасная память, и мне действительно очень жаль, но я часами гуглял, и ничего не вышло. Есть идеи?

Ответы [ 16 ]

166 голосов
/ 24 ноября 2008

На самом низком уровне (в аппаратном обеспечении), да, , если с дороги. Чтобы понять почему, вы должны понять, как работают конвейеры .

Текущая команда, которая должна быть выполнена, хранится в чем-то, что обычно называется указатель инструкции (IP) или программный счетчик (ПК); эти термины являются синонимами, но разные термины используются с разными архитектурами. Для большинства инструкций ПК следующей инструкции - это просто текущий ПК плюс длина текущей инструкции. Для большинства архитектур RISC все инструкции имеют постоянную длину, поэтому ПК можно увеличивать на постоянную величину. Для архитектур CISC, таких как x86, инструкции могут иметь переменную длину, поэтому логика, которая декодирует инструкцию, должна выяснить, как долго текущая команда должна найти местоположение следующей инструкции.

Для команд ответвление , однако, следующая выполняемая инструкция не является следующей ячейкой после текущей инструкции. Ветви - это gotos - они сообщают процессору, где находится следующая инструкция. Ветви могут быть условными или безусловными, а целевое местоположение может быть либо фиксированным, либо вычисленным.

Условное и безусловное легко понять - условная ветвь берется только в том случае, если выполняется определенное условие (например, равно ли одно число другому); если ветвление не занято, управление переходит к следующей инструкции после ветвления, как обычно. Для безусловных ветвей ветка всегда берется. Условные ветви отображаются в операторах if и контрольных тестах циклов for и while. Безусловные ветви отображаются в бесконечных циклах, вызовах функций, возвратах функций, операторах break и continue, печально известном операторе goto и многих других (эти списки далеко не исчерпывающие).

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

Так что же все это значит для производительности? Когда процессор видит, что инструкция перехода появляется в его конвейере, он должен выяснить, как продолжать заполнять свой конвейер. Чтобы выяснить, какие инструкции следуют после ветви в потоке программы, необходимо знать две вещи: (1) будет ли выбрана ветвь и (2) цель ветвления. Понимание этого называется предсказанием ветвления , и это сложная проблема. Если процессор угадает правильно, программа продолжается на полной скорости. Если вместо этого процессор угадывает неправильно , он просто потратил некоторое время, вычисляя неправильную вещь. Теперь он должен очистить свой конвейер и перезагрузить его с инструкциями из правильного пути выполнения. Итог: большой успех производительности.

Таким образом, причина, по которой заявления являются дорогостоящими, связана с ошибочными прогнозами в ветвях . Это только на самом низком уровне. Если вы пишете код высокого уровня, вам не нужно беспокоиться об этих деталях. Вы должны заботиться об этом, только если вы пишете чрезвычайно критичный для производительности код на C или сборке. Если это так, то написание кода без ответвлений часто может превосходить код с ответвлением, даже если требуется еще несколько инструкций. Есть несколько интересных трюков, которые вы можете использовать для вычисления таких вещей, как abs(), min() и max() без ветвления.

16 голосов
/ 24 ноября 2008

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

Я бы не волновался об этом. Если вы не занимаетесь встроенным программированием, вы, вероятно, вообще не должны беспокоиться о стоимости "if". Для большинства программистов это просто не будет когда-либо быть движущим фактором в производительности вашего приложения.

14 голосов
/ 24 ноября 2008

Ветви, особенно на микропроцессорах архитектуры RISC, являются одними из самых дорогих инструкций. Это связано с тем, что на многих архитектурах компилятор предсказывает, какой путь выполнения будет выбран наиболее вероятным, и помещает эти инструкции в исполняемый файл следующим образом, чтобы они уже были в кэше ЦП, когда произойдет переход. Если ветвь идет другим путем, она должна вернуться в основную память и получить новые инструкции - это довольно дорого. На многих архитектурах RISC все инструкции являются одним циклом, за исключением ветви (которая часто составляет 2 цикла). Мы не говорим о крупных затратах здесь, так что не беспокойтесь об этом. Кроме того, компилятор будет оптимизировать лучше, чем вы, в 99% случаев :) Одна из действительно замечательных особенностей архитектуры EPIC (например, Itanium) заключается в том, что он кэширует (и начинает обрабатывать) инструкции с обеих сторон ветви, затем отбрасывает набор, который ему не нужен, когда известен результат ветвления. Это сохраняет дополнительный доступ к памяти типичной архитектуры в случае, если она разветвляется по непредсказуемому пути.

10 голосов
/ 25 ноября 2008

Ознакомьтесь со статьей Повышение производительности благодаря устранению ветвления по производительности ячеек. Еще один забавный этот пост о выборках без ответвлений в блоге обнаружения столкновений в реальном времени.

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

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

7 голосов
/ 24 ноября 2008

if само по себе не медленно. Медлительность всегда относительна, я готов поспорить, что вы никогда не ощущали «накладных расходов» в выражении «if». Если вы собираетесь создавать высокопроизводительный код, вы все равно можете избежать веток. Что делает if медленным, так это то, что процессор предварительно загружает код после if на основе некоторой эвристики и тому подобного. Он также остановит конвейеры от выполнения кода непосредственно после команды ветвления if в машинном коде, поскольку процессор еще не знает, какой путь будет выбран (в конвейерном процессоре чередуются и выполняются несколько инструкций). Выполненный код может быть выполнен в обратном порядке (если была взята другая ветвь. Она называется branch misprediction), или noop заполняется в этих местах, чтобы этого не произошло.

Если if является злом, то switch также является злом, и &&, || тоже. Не беспокойся об этом.

7 голосов
/ 24 ноября 2008

На минимально возможном уровне if состоит из (после вычисления всех специфичных для приложения предпосылок для конкретного if):

  • некоторые инструкции по тестированию
  • перейти в какое-то место в коде, если тест пройден успешно, в противном случае продолжить работу.

Затраты, связанные с этим:

  • Сравнение низкого уровня - обычно 1 процессор, супер дешево
  • потенциальный скачок - который может быть дорогим

Reson, почему прыжки стоят дорого:

  • вы можете перейти к произвольному коду, который живет где-либо в памяти, если окажется, что он не кэшируется процессором - у нас есть проблема, потому что нам нужен доступ к основной памяти, который медленнее
  • современные процессоры выполняют предварительную ветвь. Они пытаются угадать, удастся ли это или нет, и выполнить код впереди в конвейере, так что все ускорится. Если прогноз не удался, все вычисления, выполняемые по конвейеру, должны быть признаны недействительными. Это также дорогая операция

Итак, подведем итог:

  • Если возможно, если вы действительно, действительно, действительно заботитесь о производительности.
  • Вы должны заботиться об этом тогда и только тогда, когда вы пишете raytracer в реальном времени или биологические симуляции или что-то подобное. Нет причин беспокоиться об этом в большей части реального мира.
6 голосов
/ 24 ноября 2008

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

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

Pentium 4 (Prescott) имел знаменитый длинный конвейер из 31 ступени.

Подробнее о Википедии

6 голосов
/ 24 ноября 2008

Может быть, ветвление убивает предварительную выборку инструкций процессора?

4 голосов
/ 25 ноября 2008

Как отмечают многие, условные ветви могут быть очень медленными на современном компьютере.

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

4 голосов
/ 24 ноября 2008

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

Однако это крайне специфично для ситуации - большинство современных процессоров имеют возможности прогнозирования ветвлений, которые пытаются минимизировать негативные последствия ветвления. Другим примером может быть то, как архитектура ARM (и, возможно, другие) может обрабатывать условную логику - ARM имеет условное выполнение на уровне команд, поэтому простая условная логика не приводит к переходам - ​​инструкции просто выполняются как NOP, если условия не выполняются. *

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

...