it-swarm.com.ru

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

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

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

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

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

82
pek

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

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

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

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

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

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

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

166
Adam Rosenfield

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

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

16
Joel Coehoorn

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

14
rmeador

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

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

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

10
Parappa

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

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

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

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

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

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

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

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

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

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

7
Johannes Schaub - litb

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

6
activout.se

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

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

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

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

6
Guge

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

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

4
David Thornley

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

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

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

4
Michael Burr

Также обратите внимание, что внутри цикла не обязательно очень дорого.

Современный ЦП предполагает при первом посещении оператора if, что "if-body" должно быть взято (или сказано иначе: он также предполагает, что тело цикла будет взято несколько раз) (*). После второго и последующих посещений он (ЦП) может заглянуть в Таблицу истории ветвления и посмотреть, как условие было в последний раз (было ли оно правда? это было ложно?). Если в прошлый раз оно было ложным, то спекулятивное выполнение перейдет к "else" цикла if или за его пределами.

(*) Правило на самом деле " прямая ветвь не берется, обратная ветвь берется ". В операторе if есть только переход [вперед] (к точке после тела if ) если условие оценивается как ложное (помните: ЦП в любом случае предполагает не делать переход/переход), но в цикле, возможно, имеется прямая ветвь в позицию после цикла (не приниматься), и обратная ветвь при повторении (будет принято).

Это также одна из причин того, что вызов виртуальной функции или вызов указателя функции не так уж и плох, как полагают многие ( http://phresnel.org/blog/ )

3
Sebastian Mach

Процессоры глубоко конвейерны. Любая инструкция ветвления (если/для/while/switch/и т.д.) Означает, что ЦП действительно не знает, какую инструкцию загрузить и выполнить дальше.

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

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

Удачи в классе.

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

3
tfinniga

Напишите свои программы самым ясным, простым и чистым способом, который не является явно неэффективным. Это лучшее использование самого дорогого ресурса, вы. Будь то написание или последующая отладка (требует понимания) программы. Если производительности недостаточно, измерить где находятся узкие места, и посмотрите, как их устранить. Только в крайне редких случаях вам придется беспокоиться об отдельных (исходных) инструкциях при этом. Производительность заключается в выборе правильных алгоритмов и структур данных в первой строке, тщательном программировании и получении достаточно быстрой машины. Используйте хороший компилятор, вы будете удивлены, увидев, какой вид реструктуризации кода выполняет современный компилятор. Реструктуризация кода для повышения производительности является своего рода крайней мерой, код становится более сложным (таким образом ошибочным), более сложным для изменения и, следовательно, повсеместно более дорогим.

1
vonbrand

У меня был этот спор с моим другом однажды. Он использовал очень наивный алгоритм круга, но утверждал, что он быстрее моего (тип, который вычисляет только 1/8 круга), потому что мой использовал if. В конце концов, оператор if был заменен на sqrt, и это было как-то быстрее. Возможно, потому что FPU имеет встроенный sqrt?

0
Demur Rumed

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

Некоторые компиляторы предоставляют (например, GCC) их как расширение для языков программирования более высокого уровня (например, C/C++).

Обратитесь к макросам вероятно ()/вряд ли () в ядре Linux - как они работают? В чем их преимущество? .

0
Arunprasad Rajkumar