Ответ 1
assert(x*x >= 0.f)
можно написать как обещание во время компиляции вместо проверки во время выполнения, как указано ниже в GNU C:
#include <cmath>
float test1 (float x)
{
float tmp = x*x;
if (!(tmp >= 0.0f))
__builtin_unreachable();
return std::sqrt(tmp);
}
(связанный: Какие оптимизации обеспечивает __builtin_unreachable? Вы также можете заключить if(!x)__builtin_unreachable()
в макрос и назвать его promise()
или как-то еще.)
Но gcc не знает, как воспользоваться этим обещанием, что tmp
не является NaN и неотрицательным. Мы по-прежнему получаем (Godbolt) ту же последовательность готовых asm, которая проверяет x>=0
и в противном случае вызывает sqrtf
для установки errno
. Предположительно, что расширение в сравнение и ветвление происходит после других этапов оптимизации,, поэтому компилятору не поможет узнать больше.
Это пропущенная оптимизация в логике, которая умозрительно указывает на sqrt
, когда -fmath-errno
включен (к сожалению, включен по умолчанию).
Вместо этого вы хотите -fno-math-errno
, который безопасен во всем мире
Это на 100% безопасно, если вы не полагаетесь на математические функции, которые когда-либо устанавливали errno
. Никто не хочет этого, для чего предназначены распространение NaN и/или липкие флаги, которые записывают скрытые исключения FP. например C99/C++ 11 fenv
доступ через #pragma STDC FENV_ACCESS ON
и затем работает как fetestexcept()
. См. пример в feclearexcept
, где показано его использование для обнаружения деления на ноль.
Среда FP является частью контекста потока, в то время как errno
является глобальным.
Поддержка этой устаревшей несостоятельности не является бесплатной; Вы должны просто отключить его, если у вас нет старого кода, который был написан для его использования. Не используйте его в новом коде: используйте fenv
. В идеале поддержка -fmath-errno
была бы настолько дешевой, насколько это возможно, но редкость того, кто на самом деле использует __builtin_unreachable()
или другие вещи, чтобы исключить ввод NaN, по-видимому, не стоила времени для разработчиков на реализацию оптимизации. Тем не менее, вы можете сообщить об ошибке пропущенной оптимизации, если хотите.
Реальное аппаратное обеспечение FPU на самом деле имеет эти липкие флаги, которые остаются установленными до сброса, например, x86 mxcsr
регистр состояния/управления для математики SSE/AVX или аппаратных FPU в других ISA. На оборудовании, где FPU может обнаруживать исключения, качественная реализация C++ будет поддерживать такие вещи, как fetestexcept()
. А если нет, то math- errno
, вероятно, тоже не работает.
errno
для математики был старым устаревшим проектом, с которым C/C++ по-прежнему придерживался по умолчанию, и теперь широко считается плохой идеей. Это усложняет компиляторам эффективную интеграцию математических функций. Или, может быть, мы не настолько застряли с этим, как я думал: Почему errno не установлен в EDOM, даже если sqrt снимает спор о домене? объясняет, что установка errno в математических функциях необязательна в ISO C11, и реализация может указывать, делают ли они это или нет. Предположительно и в C++.
Было бы большой ошибкой смешивать -fno-math-errno
с оптимизациями, меняющими значение, такими как -ffast-math
или -ffinite-math-only
. Вам настоятельно рекомендуется включить его глобально или, по крайней мере, для всего файла, содержащего эту функцию.
float test2 (float x)
{
return std::sqrt(x*x);
}
# g++ -fno-math-errno -std=gnu++17 -O3
test2(float): # and test1 is the same
mulss xmm0, xmm0
sqrtss xmm0, xmm0
ret
Вы также можете использовать -fno-trapping-math
, если вы никогда не собираетесь снимать маску с каких-либо исключений FP с помощью feenableexcept()
. (Хотя эта опция не требуется для этой оптимизации, проблема только в errno
-setting.)
-fno-trapping-math
не предполагает отсутствие NaN или чего-либо еще, а только то, что исключения FP, такие как Invalid или Inexact, никогда не будут вызывать обработчик сигнала вместо создания NaN или округленного результата. -ftrapping-math
является значением по умолчанию, но он сломан и "никогда не работал", по словам разработчика GCC Марка Глисса. (Даже если он включен, GCC выполняет некоторые оптимизации, которые могут изменить количество исключений, которые будут повышены с нуля до ненулевого значения или наоборот. И это блокирует некоторые безопасные оптимизации). Но, к сожалению, https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54192 (отключить по умолчанию) все еще открыт.
Если вы на самом деле когда-либо снимали исключения, возможно, было бы лучше иметь -ftrapping-math
, но, опять же, очень редко вы захотите этого вместо простой проверки флагов после некоторых математических операций или проверки на NaN. И это на самом деле не сохраняет точную семантику исключений в любом случае.
См. SIMD для операции с пороговым значением с плавающей запятой для случая, когда -fno-trapping-math
неправильно блокирует безопасную оптимизацию. (Даже после подъема потенциально ловушечной операции, так что C делает это безоговорочно, gcc создает не векторизованный asm, который делает это условно! Таким образом, он не только блокирует векторизацию, но и меняет семантику исключений по сравнению с абстрактной машиной C.)