Будет ли спекулятивное исполнение следовать дорогостоящей операции?

Если я правильно понимаю ветвление (x86), процессор иногда умозрительно выбирает путь к коду и выполняет инструкции и "отменяет" результаты неверного пути. Что делать, если операция в неправильном кодовом пути очень дорогая, например чтение памяти, которое приводит к потере кэша или какой-либо дорогой математической операции? Будет ли процессор пытаться выполнить что-то дорогое раньше времени? Как процессор обычно справляется с этим?

if (likely) {
    // do something lightweight (addition, subtraction, etc.)
} else {
    // do something expensive (cache-miss, division, sin/cos/tan etc.)
}

1 ответ

Решение

tl: dr: воздействие не так плохо, как вы думаете, потому что процессору больше не приходится ждать медленных вещей, даже если он не отменяет их. Почти все в значительной степени передается по конвейеру, поэтому многие операции могут выполняться одновременно. Не спекулятивные операции не мешают запуску новых.


Текущие проекты x86 не спекулируют на обеих сторонах ветви одновременно. Они только спекулируют на предсказанном пути.

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


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

На других, кроме P4, фиктивных мопах в ROB/ планировщике отбрасываются при обнаружении неправильного прогноза. Из документа микроархиста Агнера Фога, говорящего о P4 против других уарчей:

штраф за неверное предсказание необычайно высок по двум причинам... [длинный конвейер и] ... ложные мопы в неправильно предсказанной ветке не сбрасываются до выхода на пенсию. Ошибочное прогнозирование обычно включает 45 мкс. Если эти мопы являются делениями или другими длительными операциями, то неправильное прогнозирование может быть чрезвычайно дорогостоящим. Другие микропроцессоры могут отбрасывать μop, как только будет обнаружено неправильное предсказание, чтобы они не использовали ресурсы выполнения без необходимости.

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

Почти все исполнительные блоки, кроме делителя, полностью конвейеризированы, так что еще одно умножение, перемешивание или что угодно может начаться без отмены 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, когда блок находится в конце страницы. Я не уверен, когда эта техника необходима. Может быть, если есть условная ветвь, которая всегда используется? Это не имеет смысла, вы просто используете безусловную ветвь. Должно быть что-то, о чем я не помню, когда ты это сделал.

Другие вопросы по тегам