Влияние прогноза ветвления на производительность?

Когда я пишу какой-то жесткий цикл, который должен работать быстро, меня часто беспокоят мысли о том, как будет вести себя прогнозирование ветвлений процессора. Например, я стараюсь избегать наличия оператора if в самом внутреннем цикле, особенно с результатом, который не является единообразным (скажем, оценивается как true или false randomly).

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

Мой вопрос: действительно ли это проблема с современными процессорами? Насколько хорошо может ожидать прогнозирование отрасли?
Какие шаблоны кодирования можно использовать для улучшения?

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

Ответы

Ответ 1

Прогнозирование ветвей в наши дни довольно чертовски хорошо. Но это не означает, что наказание ветвей можно устранить.

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

Один из них - простая латентность ветвей. На общем ПК-процессоре, который может быть порядка 12 циклов для неверного предсказания, или 1 цикл для правильно предсказанной ветки. Ради аргумента, позвольте предположить, что все ваши ветки правильно предсказаны, тогда вы свободны дома, не так ли? Не совсем.

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

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

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

Но в типичном коде ветки по существу свободны. В типичном коде не так много веток тесно связаны друг с другом в критическом по производительности коде.

Ответ 2

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

Ответ 3

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

Отлично. Затем вы можете профилировать производительность своего приложения, использовать gcc-теги, чтобы снова сделать прогноз и профиль, использовать теги gcc, чтобы снова сделать противоположное предсказание и профиль.

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

Если вы обнаружите, что половина ваших префетов потеряна, теряя 5% от вашего процессорного времени, тогда вы хотите искать решение, которое не введет ветку.

Ответ 4

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

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

Ответ 5

Да, предсказание ветки действительно может быть проблемой производительности.

Этот вопрос (в настоящее время самый высокий вопрос по StackOverflow) дает пример.

Ответ 6

Отвечаю:

Причина, по которой AMD в какой-то момент была такой же быстрой или лучше, чем Intel, - это просто то, что у них было лучшее предсказание ветвей.

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

Итак, вывод: избегайте ветвей, если они не нужны. Если они есть, постарайтесь сделать так, чтобы одна ветвь оценивалась в 95% случаев.

Ответ 7

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

У меня было что-то вроде следующего в замкнутом цикле:

if (var >= limit) { otherVar = 0;}

Я хотел избавиться от потенциальной ветки и попытался изменить ее на:

otherVar *= (var<limit)&1;

Но "оптимизация" генерировала в два раза больше сборки и была на самом деле медленнее.