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

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

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

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

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

Ответ 1

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

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

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

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

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

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

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

Ответ 2

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

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

Ответ 3

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

Ответ 4

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

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

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

Ответ 5

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

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

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

Подробнее о Wikipedia

Ответ 6

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

Ответ 7

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

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

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

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

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

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

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

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

Ответ 8

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

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

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

Ответ 9

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

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

Ответ 10

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

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

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

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

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

Ответ 11

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

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

(*) На самом деле правило " форвардная ветвь не взята, обратная ветвь принята". В if-заявлении есть только [вперед] скачок (до точки после if-body), если условие оценивается как false (помните: CPU в любом случае предполагает, что он не принимает ветку/прыжок), но в цикле возможно наличие прямой ветки в позицию после цикла (не для взятия) и обратную ветвь после повторения (чтобы ее принять).

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

Ответ 12

Самый дорогой с точки зрения использования ALU? Он использует регистры процессора для хранения сравниваемых значений и занимает время для извлечения и сравнения значений каждый раз, когда выполняется оператор if.

Поэтому оптимизация заключается в том, чтобы выполнить одно сравнение и сохранить результат как переменную до запуска цикла.

Просто пытаюсь интерпретировать ваши недостающие слова.

Ответ 13

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

Ответ 14

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

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

Ответ 15

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

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

Посмотрите вероятные()/маловероятные() макросы в ядре Linux - как они работают? Какова их польза?

Ответ 16

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