cvtsi2ss %edi, %xmm0
объединяет число с плавающей точкой в элемент младшего элемента XMM0, поэтому оно ложно зависит от старого значения. (При повторных вызовах одной и той же функции создается один длинный l oop -принесенный цепочка зависимостей.)
xor-zeroing разрывает цепочку dep, позволяя exe c не в порядке работать со своими волхвами c. Таким образом, вы ограничиваете пропускную способность addss
(0,5 цикла) вместо задержки (4 цикла).
Ваш ЦП является производной от Skylake, так что это числа; ранее Intel имела 3-тактную задержку, 1-тактную пропускную способность, используя выделенный исполняющий модуль FP-add вместо запуска его на модулях FMA. https://agner.org/optimize/. Вероятно, служебные вызовы call / ret препятствуют тому, чтобы вы увидели полное 8-кратное ожидаемое ускорение от произведения задержки * в полосе пропускания 8 × 102 моп в полете в конвейерных блоках FMA; вы должны получить это ускорение, если вы удалите xorps
dep-break из al oop в пределах одной функции.
G CC имеет тенденцию быть очень «осторожным» в отношении ложных зависимостей , тратя дополнительные инструкции (полосу пропускания внешнего интерфейса), чтобы на всякий случай их сломать. В коде, который является узким местом во внешнем интерфейсе (или где общий размер кода / занимаемая площадь кэша UOP является фактором), это снижает производительность, если регистр все равно действительно был готов вовремя.
Clang / LLVM безрассудный и неосторожный в этом вопросе , обычно не заботящийся о том, чтобы избежать ложных зависимостей от регистров, не записанных в текущей функции. (т.е. предполагая / делая вид, что регистры "холодные" при входе в функцию). Как показано в комментариях, clang избегает создания al oop -переданной цепочки dep путем обнуления xor при зацикливании внутри одной функции вместо нескольких вызовов одной и той же функции.
Clang даже использует 8-битный Частичные регистры GP-целочисленные без причины в некоторых случаях, когда это не сохраняет размер кода или инструкции по сравнению с 32-битными регистрами. Обычно это, вероятно, хорошо, но есть риск объединения в длинную цепочку депозита или создания цепочки зависимостей, переносимой al oop, если у вызывающей стороны (или при вызове функции родственного брата) все еще есть загрузка из-за отсутствия кэша в полете к этому регистру, когда мы Вызов, например.
См. Понимание влияния lfence на al oop с двумя длинными цепочками зависимостей, для увеличения длины для получения дополнительной информации о том, как OoO exe c может перекрывать короткие и средние длины независимые деп цепи. Также связано: Почему Мулсс занимает всего 3 цикла на Haswell, в отличие от таблиц инструкций Агнера? (Развертывание циклов FP с несколькими аккумуляторами) - это развертывание точечного продукта с несколькими аккумуляторами, чтобы скрыть задержку FMA.
https://www.uops.info/html-instr/CVTSI2SS_XMM_R32.html содержит сведения о производительности для этой инструкции в различных выпусках .
Этого можно избежать, если использовать AVX с vcvtsi2ss %edi, %xmm7, %xmm0
(где xmm7 - это любой регистр, который вы недавно не писали или который был ранее в депе цепочка, которая приводит к текущему значению EDI).
Как я уже упоминал в Почему задержка команды sqrtsd изменяется в зависимости от ввода? Процессоры Intel
Это проектирование ISA благодаря Intel, оптимизирующей в краткосрочной перспективе с SSE1 на Pentium III. P3 внутренне обрабатывал 128-битные регистры как две 64-битные половины. Оставляя верхнюю половину неизменной, пусть скалярные инструкции декодируются в один моп. (Но это все еще дает PIII sqrtss
ложную зависимость). Наконец, AVX позволяет нам избежать этого с помощью vsqrtsd %src,%src, %dst
, по крайней мере, для источников регистров, если не памяти, и аналогично vcvtsi2sd %eax, %cold_reg, %dst
для аналогично близоруких разработанных инструкций скалярного преобразования int-> fp.
(G CC пропущенная оптимизация отчеты: 80586 , 89071 , 80571 .)
Если cvtsi2ss
/ sd
обнулили верхние элементы регистров, у нас не было бы этой глупой проблемы / не нужно было бы разбрасывать инструкцию xor-zeroing; спасибо интел. (Другая стратегия состоит в том, чтобы использовать SSE2 movd %eax, %xmm0
, который делает растяжение нуля, затем упакованное преобразование int-> fp, которое работает на всем 128-битном векторе. Это может быть безубыточным для float, где int-> fp скалярное преобразование - 2 моп, а векторная стратегия - 1 + 1. Но не вдвое, когда преобразование в упаковку int-> fp стоит shuffle + FP uop.)
Это именно та проблема, которой AMD64 избегает выполнение записи в 32-разрядные целочисленные регистры неявным образом расширяется до полного 64-разрядного регистра вместо того, чтобы оставить его неизменным (иначе говоря, слияние). Почему инструкции x86-64 для 32-разрядных регистров обнуляют верхнюю часть полного 64-разрядного регистра? (запись 8- и 16-разрядных регистров do вызывает ложные зависимости на процессорах AMD и Intel со времен Haswell).