Сплавленные многократные добавления и режимы округления по умолчанию

С 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 полезна, если вам не нужна скорость, но точность. Он будет медленно вычислять сокращенный результат, но правильно, даже если он недоступен в аппаратном обеспечении. И должен быть встроен, если он доступен на аппаратном уровне.