Что на самом деле делает математика gcc?

144

Я понимаю, что --ffast-mathфлаг gcc может значительно увеличить скорость операций с плавающей запятой и выходит за рамки стандартов IEEE, но я не могу найти информацию о том, что на самом деле происходит, когда он включен. Может ли кто-нибудь объяснить некоторые детали и, возможно, дать четкий пример того, как что-то изменилось бы, если флаг был включен или выключен?

Я попытался покопаться в SO для похожих вопросов, но не смог найти ничего, объясняющего работу ffast-math.

Ponml
источник

Ответы:

86

Как вы упомянули, он допускает оптимизации, которые не сохраняют строгое соответствие IEEE.

Пример таков:

x = x*x*x*x*x*x*x*x;

в

x *= x;
x *= x;
x *= x;

Поскольку арифметика с плавающей точкой не является ассоциативной, порядок и факторизация операций будут влиять на результаты из-за округления. Следовательно, эта оптимизация не выполняется при строгом поведении FP.

На самом деле я не проверял, выполняет ли GCC именно эту оптимизацию. Но идея та же самая.

Mysticial
источник
25
@ Андрей: для этого примера вы идете от 7 умножений до 3.
Мистик
4
@ Андрей: Математически, это будет правильно. Но результат может немного отличаться в последних нескольких битах из-за разного округления.
Мистик
1
В большинстве случаев это небольшое различие не имеет значения (относительно порядка 10 ^ -16 для double, но варьируется в зависимости от приложения). Стоит отметить, что оптимизация ffast-math не обязательно добавляет округление «больше». Единственная причина, почему он не соответствует IEEE, заключается в том, что ответ отличается (хотя и немного) от того, что написано.
Мистик
1
@user: Величина ошибки зависит от входных данных. Он должен быть небольшим по отношению к результату. Например, если значение xменьше 10, ошибка в примере с Mystical будет ниже 10 ^ -10. Но если x = 10e20ошибка, вероятно, будет много миллионов.
Бен Фойгт
3
@stefanct это на самом деле о том, -fassociative-mathкоторый включен в -funsafe-math-optimizationsкотором , в свою очередь включена -ffast-math Почему не GCC оптимизируют a*a*a*a*a*aк (a*a*a)*(a*a*a)?
phuclv
256

-ffast-math делает намного больше, чем просто нарушает строгое соответствие IEEE.

Прежде всего, конечно, это нарушает строгое соответствие IEEE, позволяя, например, переупорядочивать инструкции к чему-то, что математически совпадает (в идеале), но не точно то же самое с плавающей запятой.

Во-вторых, он отключает настройку errnoпосле математических функций с одной инструкцией, что означает избегание записи в локальную переменную потока (это может иметь 100% -ное различие для этих функций на некоторых архитектурах).

В-третьих, он предполагает, что вся математика является конечной , что означает, что никакие проверки на NaN (или ноль) не производятся там, где они будут иметь вредные эффекты. Просто предполагается, что этого не произойдет.

В-четвертых, это позволяет использовать взаимные аппроксимации для деления и обратного квадратного корня.

Кроме того, он отключает знаковый ноль (код предполагает, что нулевой знак не существует, даже если цель его поддерживает) и математика округления, что позволяет, среди прочего, выполнять постоянное свертывание во время компиляции.

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

Damon
источник
15
Дэймон, спасибо! Можете ли вы добавить некоторые ссылки? Например, gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html " -ffast-math Устанавливает -fno-math-errno, -funsafe-math-optimizations, -ffinite-math-only, -fno-rounding-math, -fno-signaling -nans и -fcx-limited-range. Этот параметр вызывает определение макроса препроцессора FAST_MATH . "и что-то из glibc, например ( math.hрядом с math_errhandling)". По умолчанию все функции поддерживают обработку как errno, так и обработки исключений. В быстром математическом режиме gcc и если определены встроенные функции, это может быть не так. "
osgx
4
@javapowered: «Опасно» ли это, зависит от того, какие гарантии вам нужны. -ffast-mathпозволяет компилятору срезать некоторые углы и нарушать некоторые обещания (как объяснено), что в целом не опасно как таковое и не является проблемой для большинства людей. Для большинства людей это то же самое, только быстрее. Однако, если ваш код предполагает и полагается на эти обещания, тогда ваш код может вести себя не так, как вы ожидаете. Как правило, это означает , что программа будет , кажется , работает хорошо, в основном, но некоторые результаты могут быть «неожиданными» (скажем, в симуляции физики, два объекта может не Collide должным образом).
Деймон
2
@ Ройи: оба должны быть независимы друг от друга. -O2как правило, включает «каждую» легальную оптимизацию, за исключением тех, которые обменивают размер на скорость. -O3также позволяет оптимизировать этот размер для скорости. Это все еще поддерживает 100% -ую корректность. -ffast-mathпытается ускорить математические операции, допуская «слегка некорректное» поведение, которое обычно не вредно, но будет считаться неверным в формулировке стандарта. Если ваш код действительно сильно отличается по скорости на двух компиляторах (не только на 1-2%), то убедитесь, что ваш код строго соответствует стандартам и ...
Damon
1
... выдает нулевые предупреждения. Кроме того, убедитесь, что вы не мешаете создавать псевдонимы и такие вещи, как автоматическая векторизация. В принципе, GCC должен работать по крайней мере так же хорошо (обычно по моему опыту лучше), как MSVC. Если это не так, вы, вероятно, допустили небольшую ошибку, которую MSVC просто игнорирует, но которая заставляет GCC отключить оптимизацию. Вы должны дать оба варианта, если вы хотите их обоих, да.
Деймон
1
@Royi: Этот код для меня не выглядит очень маленьким и простым, не то, что можно было бы глубоко проанализировать за несколько минут (или даже часов). Среди прочего, он включает в себя, казалось бы, безвредность #pragma omp parallel for, и в теле цикла вы читаете и пишете по адресам, указанным аргументами функции, и выполняете нетривиальное количество ветвлений. Как необразованное предположение, вы могли бы перебивать кеши из-за определенного вами потока реализации, и MSVC может некорректно избегать промежуточных хранилищ, которые предписывают правила наложения имен. Невозможно сказать.
Деймон