В частности, если у меня есть ряд if
... else if
утверждений, и я каким-то образом заранее знаю относительную вероятность, по которой будет оцениваться каждое утверждение true
, насколько сильно различается время выполнения для их сортировки в порядке вероятности? Например, я должен предпочесть это:
if (highly_likely)
//do something
else if (somewhat_likely)
//do something
else if (unlikely)
//do something
к этому?:
if (unlikely)
//do something
else if (somewhat_likely)
//do something
else if (highly_likely)
//do something
Кажется очевидным, что отсортированная версия будет быстрее, однако из-за читабельности или наличия побочных эффектов мы можем захотеть упорядочить их неоптимально. Также трудно сказать, насколько хорошо процессор будет делать с предсказанием ветвлений, пока вы на самом деле не запустите код.
Таким образом, в ходе экспериментов с этим я в конечном итоге отвечал на свой собственный вопрос для конкретного случая, однако я также хотел бы услышать другие мнения / идеи.
Важно: этот вопрос предполагает, что if
операторы могут быть произвольно переупорядочены без каких-либо других последствий для поведения программы. В моем ответе три условных теста являются взаимоисключающими и не дают побочных эффектов. Конечно, если утверждения должны оцениваться в определенном порядке для достижения желаемого поведения, тогда вопрос эффективности является спорным.
Ответы:
Как правило, большинство, если не все процессоры Intel, предполагают, что прямые ветви не берутся с первого раза, когда они их видят. Смотрите работу Годболта .
После этого ветвь переходит в кэш предсказания ветвления, и для информирования о будущем предсказании ветвления используется прошлое поведение.
Таким образом, в тесном цикле, эффект неправильного порядка будет относительно небольшим. Предиктор ветвей собирается узнать, какой набор веток наиболее вероятен, и если у вас нет нетривиального объема работы в цикле, небольшие различия не будут сильно складываться.
В общем коде большинство компиляторов по умолчанию (без другой причины) упорядочивают полученный машинный код примерно так, как вы его упорядочили в своем коде. Таким образом, если заявления являются прямыми ветвями, когда они терпят неудачу
Таким образом, вы должны упорядочить свои ветви в порядке уменьшения вероятности, чтобы получить лучший прогноз ветвления из «первого столкновения».
Микробенчмарк, который многократно зацикливается на множестве условий и выполняет тривиальную работу, будет зависеть от крошечных эффектов подсчета команд и тому подобного, а также от проблем, связанных с предсказанием относительных переходов. Так что в этом случае вы должны зарегистрироваться , так как практические правила не будут надежными.
Кроме того, векторизация и многие другие оптимизации применяются к крошечным узким циклам.
Итак, в общем коде, поместите наиболее вероятный код в
if
блок, и это приведет к наименьшему количеству пропущенных некэшируемых предсказаний ветвления. В тесных циклах следуйте общему правилу, чтобы начать, и если вам нужно знать больше, у вас нет другого выбора, кроме как профилировать.Естественно, все это выходит за рамки, если некоторые тесты намного дешевле, чем другие.
источник
Я составил следующий тест, чтобы рассчитать время выполнения двух разных
if
...else if
блоков, один из которых отсортирован по вероятности, а другой - в обратном порядке:При использовании MSVC2017 с / O2 результаты показывают, что отсортированная версия всегда примерно на 28% быстрее, чем несортированная версия. Согласно комментарию luk32, я также поменял порядок двух тестов, что делает заметную разницу (22% против 28%). Код был запущен под Windows 7 на Intel Xeon E5-2697 v2. Это, конечно, очень специфично для проблемы и не должно интерпретироваться как окончательный ответ.
источник
if... else if
оператора может существенно повлиять на то, как логика протекает через код.unlikely
Проверка не может прийти часто, но не может быть бизнес - необходимость для проверкиunlikely
состояния первого перед проверкой для других.g++ -O2 -march=native -std=c++14
дает небольшое преимущество отсортированным условным операторам, но в большинстве случаев разница в процентах между двумя запусками составляла ~ 5%. Несколько раз это было на самом деле медленнее (из-за различий). Я вполне уверен, что заказываяif
s не стоит беспокоиться; PGO, вероятно, полностьюНет, вы не должны, если вы действительно не уверены, что целевая система затронута. По умолчанию идут по читабельности.
Я очень сомневаюсь в ваших результатах. Я немного изменил ваш пример, чтобы отменить выполнение стало проще. Ideone довольно последовательно показывает, что обратный порядок быстрее, хотя и не намного. На некоторых трассах даже это иногда переворачивается. Я бы сказал, что результаты неубедительны. Coliru также не сообщает никакой разницы. Я могу проверить процессор Exynos5422 на моем odroid xu4 позже.
Дело в том, что современные процессоры имеют предикторы ветвления. Существует много логики, предназначенной для предварительной выборки как данных, так и инструкций, и современные процессоры x86 достаточно умны, когда дело доходит до этого. Некоторые более тонкие архитектуры, такие как ARM или GPU, могут быть уязвимы для этого. Но это действительно сильно зависит как от компилятора, так и от целевой системы.
Я бы сказал, что оптимизация порядка веток довольно хрупкая и эфемерная. Делайте это только как действительно точный шаг настройки.
Код:
источник
Просто мои 5 центов. Кажется, эффект упорядочения, если заявления должны зависеть от:
Вероятность каждого оператора if.
Количество итераций, чтобы можно было использовать предсказатель ветвления.
Вероятные / маловероятные подсказки компилятора, то есть расположение кода.
Чтобы изучить эти факторы, я проверил следующие функции:
ordered_ifs ()
reversed_ifs ()
ordered_ifs_with_hints ()
reversed_ifs_with_hints ()
данные
Массив данных содержит случайные числа от 0 до 100:
Результаты
Следующие результаты приведены для Intel i5 @ 3,2 ГГц и G ++ 6.3.0. Первый аргумент - это check_point (т. Е. Вероятность в %% для весьма вероятного оператора if), второй аргумент - data_sz (т. Е. Количество итераций).
Анализ
1. Порядок имеет значение
Для 4K итераций и (почти) 100% вероятности очень понравившегося утверждения разница огромна - 223%:
Для 4K итераций и 50% вероятности очень понравившегося утверждения разница составляет около 14%:
2. Количество итераций имеет значение
Разница между 4K и 8K итерациями для (почти) 100% вероятности очень понравившегося утверждения составляет около двух раз (как и ожидалось):
Но разница между 4K и 8K итерациями для 50% вероятности очень популярного утверждения составляет 5,5 раз:
Почему так? Из-за ветки предсказатель промахивается. Вот ветка промахов для каждого упомянутого выше случая:
Таким образом, на моем i5 предиктор ветвлений не работает с невероятной вероятностью ветвлений и больших наборов данных.
3. Подсказки помогают немного
Для итераций 4K результаты несколько хуже для вероятности 50% и несколько лучше для вероятности, близкой к 100%:
Но для итераций 8K результаты всегда немного лучше:
Так что подсказки тоже помогают, но чуть-чуть.
Общий вывод: всегда проверяйте код, потому что результаты могут удивить.
Надеюсь, это поможет.
источник
g++ -O2
или-O3 -fno-tree-vectorize
, но вы должны сказать так.Основываясь на некоторых других ответах здесь, похоже, что единственный реальный ответ: это зависит . Это зависит как минимум от следующего (хотя и не обязательно в этом порядке важности):
Единственный способ узнать наверняка - это сравнить ваш конкретный случай, предпочтительно в системе, идентичной (или очень похожей) на предполагаемую систему, в которой в конечном итоге будет выполняться код. Если он предназначен для работы на множестве разных систем с различным аппаратным обеспечением, операционной системой и т. Д., То рекомендуется сравнить несколько вариантов, чтобы определить, какой из них лучше. Может быть даже хорошей идеей, чтобы код был скомпилирован с одним порядком в системе одного типа и другим порядком в системе другого типа.
Моё личное эмпирическое правило (в большинстве случаев при отсутствии эталона) это заказ на основе:
источник
То, как я обычно это решаю для высокопроизводительного кода, заключается в поддержании порядка, который наиболее читабелен, но дает подсказки компилятору. Вот один пример из ядра Linux :
Здесь предполагается, что проверка доступа пройдет, и что ошибка не возвращается
res
. Пытаясь изменить порядок любой из них , если положения будут только запутать код, ноlikely()
иunlikely()
макросы на самом деле помогают читаемость, указывая на то , что это обычный случай и то , что является исключением.Реализация этих макросов в Linux использует специфические особенности GCC . Кажется, что clang и компилятор Intel C поддерживают один и тот же синтаксис, но MSVC не имеет такой возможности .
источник
likely()
иunlikely()
макросы определены, и включают в себя некоторую информацию о соответствующей функции компилятора.else if
если компилятор не достаточно умен, чтобы знать, что условия являются взаимоисключающими.Также зависит от вашего компилятора и платформы, для которой вы компилируете.
Теоретически, наиболее вероятное условие должно сделать скачок управления как можно меньше.
Обычно наиболее вероятное условие должно быть первым:
Самые популярные ASM основаны на условных ветвлений , которые прыгают , когда условие истинно . Этот код на C, скорее всего, будет переведен в такую псевдо-асм:
Это связано с тем, что переходы заставляют процессор отменять конвейер выполнения и останавливаться из-за изменения счетчика программы (для архитектур, которые поддерживают конвейеры, которые действительно распространены). Затем речь идет о компиляторе, который может применять, а может и не применять некоторые сложные оптимизации, касающиеся наличия статистически наиболее вероятного условия, чтобы элемент управления выполнял меньше переходов.
источник
clang
самом деле использовался другой подход дляtest2
иtest3
: из-за эвристики, которая указывает на то, что тест< 0
или,== 0
вероятно, будет ложным, он решил клонировать оставшуюся часть функции на обоих путях, чтобы он мог выполнитьcondition == false
путь сквозного падения. Это возможно только потому, что оставшаяся часть функции короткая:test4
я добавил еще одну операцию, и она вернулась к подходу, описанному выше.jmp
не являются полезно, так что пропускная способность выборки / декодирования теряется (2) даже при прогнозировании современные большие ядра делают только одну выборку за цикл, поэтому он устанавливает жесткий предел в 1 взятый переход / цикл (OTOH современный Intel может сделать 2 не взятых / цикл) (3 ) предсказанию ветвлений сложнее иметь дело с взятыми последовательными ветвлениями, а в случае быстрых + медленных предикторов ...Я решил повторить тест на своей машине, используя код Lik32. Я должен был изменить это из-за моих окон или компилятора, думая, что высокое разрешение составляет 1 мс, используя
mingw32-g ++. exe -O3 -Wall -std = c ++ 11 -fexceptions -g
GCC произвел одинаковое преобразование в обоих исходных кодах.
Обратите внимание, что тестируются только два первых условия, поскольку третье всегда должно быть верным, GCC здесь является своего рода Шерлоком.
Обеспечить регресс
Так что это мало что нам говорит, за исключением того, что в последнем случае не требуется прогнозирование ветвления.
Сейчас я перепробовал все 6 комбинаций if, первые 2 - обратный и отсортированный. высокий>> 95, низкий <20, средний 20-94 с 10000000 итераций каждая.
Так почему порядок выше, ниже, меньше, чем медленнее (незначительно)
Потому что самый непредсказуемый последний и поэтому никогда не запускается через предиктор ветвления.
Таким образом, ветви будут предсказаны, взяты, взяты и оставлены с
6% + (0,94 *) 20% неверно предсказывает.
«Сортировка»
Ветви будут предсказаны с не взятым, не взятым и Шерлоком.
25% + (0,75 *) 24% ошибочно прогнозируют
Разница составляет 18-23% (измеренная разница ~ 9%), но нам нужно вычислять циклы вместо того, чтобы неправильно прогнозировать%.
Давайте предположим, что 17 циклов неверно предсказывают штраф на моем процессоре Nehalem, и что каждая проверка занимает 1 цикл для выдачи (4-5 инструкций), а цикл также занимает один цикл. Зависимости данных - это счетчики и переменные цикла, но как только неправильные прогнозы исчезнут, это не должно влиять на время.
Таким образом, для «обратного» мы получаем время (это должна быть формула, используемая в компьютерной архитектуре: количественный подход IIRC).
и то же самое для "отсортировано"
(8,26-7,24) / 8,26 = 13,8% против ~ 9% измеренных (близко к измеренным!?!).
Так что очевидное из ОП не очевидно.
С этими тестами другие тесты с более сложным кодом или большим количеством зависимостей от данных, безусловно, будут отличаться, поэтому оцените ваш случай.
Изменение порядка тестирования изменило результаты, но это могло быть из-за различных выравниваний начала цикла, которые в идеале должны быть выровнены на 16 байтов на всех новых процессорах Intel, но не в этом случае.
источник
Разместите их в любом логическом порядке. Конечно, ветвление может быть медленнее, но ветвление не должно быть основной работой вашего компьютера.
Если вы работаете над критичной для производительности частью кода, то, конечно, используйте логический порядок, оптимизацию по профилю и другие методы, но для общего кода, я думаю, это действительно скорее стилистический выбор.
источник
i++
когда++i
это сделало бы, потому что я знаю, чтоi++
для некоторых итераторов трудно оптимизировать,++i
и разница (для меня) не имеет значения. Речь идет об избежании пессимизации; размещение наиболее вероятного блока первым в качестве привычки по умолчанию не приведет к заметному снижению читабельности (и может даже помочь!), в то время как в результате получится код, дружественный к предсказанию ветвлений (и, таким образом, обеспечивающий равномерное небольшое повышение производительности, которое невозможно восстановить более позднейЕсли вы уже знаете относительную вероятность оператора if-else, то для повышения производительности было бы лучше использовать отсортированный способ, поскольку он будет проверять только одно условие (истинное).
Несортированным способом компилятор проверит все условия без необходимости и займет время.
источник