Сплавленные многократные добавления и режимы округления по умолчанию
С GCC 5.3 следующий компилятор кода с -O3 -fma
float mul_add(float a, float b, float c) {
return a*b + c;
}
создает следующую сборку
vfmadd132ss %xmm1, %xmm2, %xmm0
ret
Я заметил, что GCC делает это с -O3
уже в GCC 4.8.
Clang 3.7 с -O3 -mfma
производит
vmulss %xmm1, %xmm0, %xmm0
vaddss %xmm2, %xmm0, %xmm0
retq
но Clang 3.7 с -Ofast -mfma
создает тот же код, что и GCC, с -O3 fast
.
Я удивлен, что GCC делает с -O3
, потому что из этого ответа он говорит
Компилятору не разрешается спланировать разделяемое добавление и умножение, если вы не разрешаете расслабленную модель с плавающей запятой.
Это связано с тем, что FMA имеет только одно округление, а ADD + MUL - два. Таким образом, компилятор будет нарушать строгие IEEE-операции с плавающей запятой путем слияния.
Однако из эта ссылка говорится
Независимо от значения FLT_EVAL_METHOD, любое выражение с плавающей запятой может быть сжато, то есть рассчитано так, как если бы все промежуточные результаты имели бесконечный диапазон и точность.
Итак, теперь я смущен и обеспокоен.
- Является ли GCC оправданным при использовании FMA с
-O3
?
- Прерывает ли плавкое строковое поведение с плавающей запятой IEEE?
- Если fusing нарушает IEEE с плавающей точкой beahviour, и поскольку GCC возвращает
__STDC_IEC_559__
, это не противоречие?
Так как FMA можно эмулировать в программном обеспечении, похоже, должно быть два переключателя компилятора для FMA: один, чтобы сообщить компилятору использовать FMA в вычислениях, а один - сообщите компилятору, что аппаратное обеспечение имеет FMA.
Возможно, это можно контролировать с помощью опции -ffp-contract
. С GCC значением по умолчанию является -ffp-contract=fast
, а Clang - нет. Другие опции, такие как -ffp-contract=on
и -ffp-contract=off
, не производят инструкцию FMA.
Например, Clang 3.7 с -O3 -mfma -ffp-contract=fast
создает vfmadd132ss
.
Я проверил некоторые перестановки #pragma STDC FP_CONTRACT
, установленные на ON
и OFF
, с -ffp-contract
, установленными на ON
, OFF
и fast
. Во всех случаях я также использовал -O3 -mfma
.
С GCC ответ прост. #pragma STDC FP_CONTRACT
ВКЛ или ВЫКЛ не имеет значения. Только -ffp-contract
имеет значение.
GCC использует fma
с
-
-ffp-contract=fast
(по умолчанию).
В Clang используется fma
- с
-ffp-contract=fast
.
- с
-ffp-contract=on
(по умолчанию) и #pragma STDC FP_CONTRACT ON
(по умолчанию - OFF
).
Другими словами, с помощью Clang вы можете получить fma
с помощью #pragma STDC FP_CONTRACT ON
(поскольку -ffp-contract=on
является значением по умолчанию) или с помощью -ffp-contract=fast
. -ffast-math
(и, следовательно, -Ofast
) установите -ffp-contract=fast
.
Я посмотрел в MSVC и ICC.
В MSVC используется команда fma с /O2 /arch:AVX2 /fp:fast
. С MSVC /fp:precise
по умолчанию.
С ICC он использует fma с -O3 -march=core-avx2
(acctually -O1
). Это связано с тем, что по умолчанию ICC использует -fp-model fast
. Но ICC использует fma даже с -fp-model precise
. Для отключения fma с использованием ICC используйте -fp-model strict
или -no-fma
.
Таким образом, по умолчанию GCC и ICC используют fma при включении fma (с -mfma
для GCC/Clang или -march=core-avx2
с ICC), но Clang и MSVC этого не делают.
Ответы
Ответ 1
Это не нарушает IEEE-754, потому что IEEE-754 отменил языки в этой точке:
Стандарт языка также должен определять и требовать реализации, чтобы предоставлять атрибуты, которые позволяют и запрещают оптимизацию изменения стоимости, отдельно или коллективно, для блока. Эти оптимизации могут включать, но не ограничиваются:
...
- Синтез операции fusedMultiplyAdd из умножения и добавления.
В стандарте C прагма STDC FP_CONTRACT
предоставляет средства для управления этой оптимизацией изменения стоимости. Таким образом, GCC имеет лицензию на выполнение слияния по умолчанию, если он позволяет отключить оптимизацию, установив STDC FP_CONTRACT OFF
. Не поддерживать это означает не придерживаться стандарта C.
Ответ 2
Когда вы процитируете, что разрешено добавление многократного добавления, вы исключили важное условие "если только прагма FP_CONTRACT не выключена". Это новая функция в C (я думаю, представленная на C99) и была абсолютно необходима PowerPC, все из которых были скомпилированы с самого начала - фактически, x * y был эквивалентен fma (x, y, 0) и x + y эквивалентно fma (1.0, x, y).
FP_CONTRACT - это то, что контролирует размножение/добавление, а не FLT_EVAL_METHOD. Хотя если FLT_EVAL_METHOD допускает более высокую точность, то заключение договоров всегда законно; просто делайте вид, что операции выполнялись с очень высокой точностью, а затем округлены.
Функция fma полезна, если вам не нужна скорость, но точность. Он будет медленно вычислять сокращенный результат, но правильно, даже если он недоступен в аппаратном обеспечении. И должен быть встроен, если он доступен на аппаратном уровне.