Почему clang выдает 32-битную инструкцию ps с плавающей запятой для абсолютного значения 64-битной двойной точности?

Почему лязг крутится fabs(double) в вместо (как это делает GCC)?


Пример из обозревателя компилятора:

      #include <math.h>

double float_abs(double x) {
    return fabs(x);
}

лязг 12.0.1

      .LCPI0_0:
        .quad   0x7fffffffffffffff              # double NaN
        .quad   0x7fffffffffffffff              # double NaN
float_abs(double):                          # @float_abs(double)
        vandps  xmm0, xmm0, xmmword ptr [rip + .LCPI0_0]
        ret

gcc 11.2 -std=gnu++11 -Wall -O3 -march=znver3

      float_abs(double):
        vandpd  xmm0, xmm0, XMMWORD PTR .LC0[rip]
        ret
.LC0:
        .long   -1
        .long   2147483647
        .long   0
        .long   0

(По иронии судьбы, GCC использует vandpd но определяет константу с 32-битным .long chunks (что интересно с верхней половиной нуля), а clang использует vandps но определяет константу как два .quad половинки.

1 ответ

TL:DR: Наверное, потому, что оптимизатору / генератору кода проще делать это всегда, а не только с помощью устаревших инструкций SSE для сохранения размера кода. Нет никаких недостатков в производительности, и они архитектурно эквивалентны (то есть без разницы в правильности).


Вероятно, clang всегда «нормализует» архитектурно эквивалентные инструкции к их версии, потому что они имеют более короткую кодировку машинного кода для устаревших версий SSE.

Ни один из существующих процессоров x86 не имеет задержки задержки обхода для пересылки между и pdинструкция 1 , поэтому всегда безопасно использовать [v]andps между [v]mulpd или [v]fmadd...pd инструкции.

В чем смысл инструкций SSE2, таких как orpd?указывает, такие инструкции, как movupd и являются совершенно бесполезной пустой тратой места, существующей только для согласованности декодера: 66префикс перед кодом операции SSE1 всегда выполняет его версию pd. Возможно, было бы разумнее сохранить часть этого пространства кода для других будущих расширений, но Intel этого не сделала.

Или, возможно, мотивацией была будущая возможность ЦП, который действительно имел отдельные домены SIMD-double и SIMD-float, так как это было раннее время для FP SIMD Intel в целом, когда SSE2 проектировался на бумаге. В наши дни мы можем сказать, что это маловероятно, потому что блоки FMA используют много транзисторов и, по-видимому, могут быть построены для совместного использования некоторого аппаратного умножителя мантиссы между одной 53-битной мантиссой на 64-битный элемент по сравнению с двумя 23-битными мантиссами на 2x 32- битовые элементы.

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


Нет никаких преимуществ psдля версий с кодировкой AVX VEX, но также не имеет недостатков, поэтому оптимизатору / генератору кода LLVM, вероятно, проще всегда делать это, вместо того, чтобы когда-либо заботиться о том, чтобы пытаться уважать внутренние свойства источника. (Clang / LLVM, как правило, не пытается этого сделать, например, он свободно оптимизирует встроенные функции перетасовки в разные перетасовки. Часто это хорошо, но иногда деоптимизирует тщательно созданные встроенные функции, когда не знает трюка, который автор внутренности сделали.)

например, LLVM, вероятно, думает в терминах «FP-домена 128-битное побитовое И», и знает инструкцию для этого: andps / vandps. Нет причин для лязга, чтобы даже знать, что vandpd существует, потому что нет случая, когда его можно было бы использовать.


Сноска 1: Скрытые метаданные Bulldozer и пересылка между математическими инструкциями :
семейство AMD Bulldozer имеет штраф за бессмысленные вещи, такие как mulps -> mulpd, для фактических математических инструкций FP, которые действительно заботятся о компонентах знака / экспоненты / мантиссы значения FP (не логических значений или перемешивания).

По сути, никогда не имеет смысла рассматривать конкатенацию двух значений FP IEEE binary32 как двоичное 64, так что это не проблема, которую нужно решать. В основном это просто то, что дает нам представление о том, как могут быть спроектированы внутренние компоненты процессора.

В разделе Bulldozer-family руководства Agner Fog по микроархитектуре он объясняет, что задержка обхода для пересылки между двумя математическими инструкциями, которые выполняются на модулях FMA, на 1 цикл меньше, чем если бы другая инструкция находилась на пути. например addps / orps / addps имеет задержку хуже, чем addps / addps / orps, предполагая, что эти три инструкции образуют цепочку зависимостей.

Но для такой безумной вещи, как addps / addpd / orps, вы получаете дополнительную задержку. Но не для addps / orps / addpd. ( orps vs здесь никогда не имеет значения. shufps также было бы эквивалентно.)

Вероятное объяснение состоит в том, что BD хранил дополнительный материал с векторными элементами для повторного использования в этом особом случае пересылки, чтобы, возможно, избежать некоторой работы по форматированию / нормализации при пересылке FMA->FMA. Если он в неправильном формате, этот оптимистический подход должен восстановить и выполнить требуемую с архитектурной точки зрения вещь, но, опять же, это происходит только в том случае, если вы действительно обрабатываете результат float FMA / add / mul как удвоения или наоборот.

addps может переадресовать в случайном порядке, например unpcklpd без промедления, так что это не доказательство наличия трех отдельных байпасных сетей или какое-либо оправдание использования (или существования) andpd / orpd.

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