Если я правильно понимаю ветвление (x86), процессор иногда умозрительно берет путь к коду и выполняет инструкции и «отменяет» результаты неверного пути. Что делать, если операция в неправильном пути к коду очень дорогая, например, чтение из памяти, которое приводит к потере кэша или какой-либо дорогой математической операции? Будет ли процессор пытаться выполнить что-то дорогое раньше времени? Как процессор обычно справляется с этим?
if (likely) {
// do something lightweight (addition, subtraction, etc.)
} else {
// do something expensive (cache-miss, division, sin/cos/tan etc.)
}
ТЛ: др: воздействие не так плохо, как вы думаете, потому что процессору больше не нужно ждать медленных вещей, даже если он не отменяет их. Почти все в значительной степени передается по конвейеру, поэтому многие операции могут выполняться одновременно. Не спекулятивные операции не мешают запуску новых.
Текущие проекты x86 делают не спекулировать на обеих сторонах ветви сразу. Они только спекулируют на предсказанном пути.
Я не знаю какой-либо конкретной микроархитектуры, которая спекулирует в обоих направлениях ветки в любых обстоятельствах, но это не значит, что их нет. Я в основном только читал x86 микроархитектуры (см. тег вики для ссылок на руководство по микроарху Агнера Фога). Я уверен, что это было предложено в научных статьях, и, возможно, даже где-то реализовано в реальном дизайне.
Я не уверен, что именно происходит в современных разработках Intel и AMD, когда обнаруживается неправильный прогноз ветвления, когда загрузка из-за пропущенного кэша или хранилище уже ожидает выполнения, или разделение занимает единицу разделения. Конечно, выполнение не по порядку не должно ждать результата, потому что от него не зависит ни одно будущее.
На других, кроме P4, фиктивных мопах в ROB / планировщике отбрасываются при обнаружении неправильного прогноза. Из документа микроархиста Агнера Фога, говорящего о P4 против других уарчей:
штраф за неверное предсказание необычайно высок по двум причинам … [длинный конвейер и] … фиктивные мопы в ошибочно предсказанной ветке не
отбрасывается, прежде чем они уйдут на пенсию. Ошибочное прогнозирование обычно включает в себя 45
микроопераций. Если эти мопы являются делениями или другими длительными операциями
тогда неправильное прогнозирование может быть чрезвычайно дорогостоящим. Другие микропроцессоры
может отказаться от мопов, как только будет обнаружено неправильное предсказание, чтобы они
не используйте ресурсы исполнения без необходимости.
мопы, которые в настоящее время занимают исполнительные блоки, это другая история:
Почти все исполнительные блоки, кроме делителя, полностью конвейеризированы, так что еще одно умножение, перемешивание или что угодно может начаться без отмены FMA в полете. (Haswell: 5 циклов задержки, два исполнительных блока, каждый из которых способен по одному на тактовую пропускную способность, для общей устойчивой пропускной способности один на 0,5 с. Это означает, что максимальная пропускная способность требует одновременной работы 10 FMA, обычно с 10 векторными аккумуляторами). Делить это интересно, хотя. Целочисленное деление — это много мопов, поэтому ошибочный прогноз ветви по крайней мере прекратит их выдачу. FP div — это всего лишь одна инструкция uop, но не полностью конвейерная, особенно в старых процессорах. Было бы полезно отменить деление FP, связывающее блок деления, но IDK, если это возможно. Если добавление возможности отмены замедлило бы обычный случай или стоило бы больше энергии, то это, вероятно, было бы опущено. Это редкий особый случай, на который, вероятно, не стоило тратить транзисторы.
x87 fsin
или что-то является хорошим примером действительно дорогой инструкции. Я не заметил этого, пока не вернулся, чтобы перечитать вопрос. Он имеет микрокодирование, поэтому, несмотря на то, что он имеет задержку 47-106 циклов (Intel Haswell), он также составляет 71-100 моп. Неправильный прогноз ветки остановил бы внешний интерфейс от выдачи оставшихся мопов и отменил бы все те, которые стоят в очереди, как я сказал для целочисленного деления. Обратите внимание, что настоящий libm
реализации обычно не используют fsin
и так далее, потому что они медленнее и менее точны, чем то, чего можно достичь в программном обеспечении (даже без SSE), IIRC.
В случае пропадания кэша это может быть отменено, что потенциально экономит полосу пропускания в кэше L3 (и, возможно, в основной памяти). Даже если нет, инструкция больше не должна удаляться, поэтому ROB не будет заполняться, ожидая ее окончания. Именно поэтому кеш пропускает выполнение OOO так сильно, но в худшем случае это просто связывание буфера загрузки или хранения. Современные процессоры могут иметь много недочетов в кеше сразу. Часто код не делает это возможным, потому что будущие операции зависят от результата загрузки, которая пропустила в кеше (например, погоня за указателем в связанном списке или дереве), поэтому множественные операции с памятью не могут быть переданы по конвейеру. Даже если неправильный прогноз ветки не отменяет большую часть операции памяти в полете, он избегает большинства худших эффектов.
Я слышал о сдаче ud2
(недопустимая инструкция) в конце блока кода, чтобы прервать предварительную выборку команды от запуска пропуска TLB, когда блок находится в конце страницы. Я не уверен, когда эта техника необходима. Может быть, если есть условная ветвь, которая всегда используется? Это не имеет смысла, вы просто используете безусловную ветвь. Должно быть что-то, о чем я не помню, когда ты это сделал.
Других решений пока нет …