Сегодня заметил странную вещь. При копировании long double
1 все gcc
, clang
и icc
генерируют инструкции fld
и fstp
с TBYTE
операндами памяти.
То есть следующая функция:
void copy_prim(long double *dst, long double *src) {
*src = *dst;
}
Создает следующую сборку:
copy_prim(long double*, long double*):
fld TBYTE PTR [rdi]
fstp TBYTE PTR [rsi]
ret
Теперь, согласно таблицам Agner, это плохой выбор для производительности, так как fld
занимает четыре операции ( ни один не объединен) и fstp
занимает колоссальные семь мопов (ни один не объединен) по сравнению, скажем, с одним объединенным моопом каждый для movaps
в/из регистра xmm
.
Интересно, что clang
начинает использовать movaps
, как только вы вставляете long double
в struct
. Следующий код:
struct long_double {
long double x;
};
void copy_ld(long_double *dst, long_double *src) {
*src = *dst;
}
Компилируется в ту же сборку с fld
/fstp
, как показано ранее для gcc
и icc
, но clang
теперь использует:
copy_ld(long_double*, long_double*):
movaps xmm0, xmmword ptr [rdi]
movaps xmmword ptr [rsi], xmm0
ret
Как ни странно, если вы вставите дополнительный элемент int
в struct
(который удваивает его размер до 32 байтов из-за выравнивания), все компиляторы генерируют код копирования только для SSE:
copy_ldi(long_double_int*, long_double_int*):
movdqa xmm0, XMMWORD PTR [rdi]
movaps XMMWORD PTR [rsi], xmm0
movdqa xmm0, XMMWORD PTR [rdi+16]
movaps XMMWORD PTR [rsi+16], xmm0
ret
Есть ли какая-либо функциональная причина для копирования значений с плавающей запятой с помощью fld
и fstp
или это просто пропущенная оптимизация?
1 Хотя long double
(т. е. число с плавающей запятой расширенной точности x86) номинально составляет 10 байт на x86, у него есть sizeof == 16
и alignof == 16
, поскольку выравнивание должно быть степенью двойки и размером обычно должен быть не меньше размера выравнивания.
atomic<double>
загрузки/сохранения: частый отказ от целочисленных регистров, даже когда CAS не нужен, толькоmov
. stackoverflow.com/questions/45055402/ - person Peter Cordes   schedule 29.11.2017struct
иногда избегает этого. Похоже, что происходит то, что скаляризация срабатывает дляgcc
, так что простая структура с однимlong double
в конечном итоге выглядит какlong double
, а затем возвращается к плохому codegen (но не на clang). Когда вы добавляете достаточно других вещей, это останавливается и переходит к обычной логике копирования структуры, которая намного лучше. Как ни странно,icc
по-прежнему странно обрабатывает некоторые более сложные, например этот. Попробуйте удалить или добавить участникаint
, и код полностью изменится. - person BeeOnRope   schedule 29.11.2017long double
это особенно плохо. (Хотя на самом деле копирование вокругdouble
с SSE2 вместо x87 также лучше, даже с-mfpmath=387
. На самом деле ALU uop отсутствует, но задержка перезагрузки хранилища выше на 1c дляfld
/fstp
, чемmovq
/movq
(SKL от Agner Fog) - person Peter Cordes   schedule 29.11.2017long double
плюс 4int
s. - person BeeOnRope   schedule 29.11.2017-march=skylake
и так далее. ICC17, кажется, распознает только-march=native
на Godbolt или, может быть, какие-то странные вещи, такие какcorei7-avx
, но неskylake-avx512
. Но это влияет на генерацию кода, только если есть какое-либо дополнение: godbolt.org/g/SttDQT) - person Peter Cordes   schedule 29.11.2017