Почему fetestexcept в C++ скомпилирован в вызов функции, а не встроен
Я оцениваю использование (очистка и запросы) исключений с плавающей запятой в критически важном для производительности/"горячем" коде. Глядя на полученный двоичный файл, я заметил, что ни GCC, ни Clang не расширяют вызов встроенной последовательностью инструкций, как я ожидал; вместо этого они, похоже, генерируют вызов библиотеки времени выполнения. Это непомерно дорого для моего приложения.
Рассмотрим следующий минимальный пример:
#include <fenv.h>
#pragma STDC FENV_ACCESS on
inline int fetestexcept_inline(int e)
{
unsigned int mxcsr;
asm volatile ("vstmxcsr" " %0" : "=m" (*&mxcsr));
return mxcsr & e & FE_ALL_EXCEPT;
}
double f1(double a)
{
double r = a * a;
if(r == 0 || fetestexcept_inline(FE_OVERFLOW)) return -1;
else return r;
}
double f2(double a)
{
double r = a * a;
if(r == 0 || fetestexcept(FE_OVERFLOW)) return -1;
else return r;
}
И вывод, произведенный GCC: https://godbolt.org/z/jxjzYY
Компилятор, кажется, знает, что он может использовать AVX-инструкции, зависящие от семейства ЦП, для цели (он использует «vmulsd» для умножения). Однако независимо от того, какие флаги оптимизации я пытаюсь использовать, он всегда будет производить гораздо более дорогой вызов функции glibc, а не сборку, которая (насколько я понимаю) должна делать то, что делает соответствующая функция glibc.
Это не жалоба, я согласен с добавлением встроенной сборки. Мне просто интересно, может ли быть тонкая разница, которую я упускаю из виду, которая может быть ошибкой в версии встроенной сборки.
1 ответ
Это необходимо для поддержки арифметики.
fetestexcept
необходимо объединить состояния SSE и FPU, потому что
long double
операции обновляют только состояние FPU, но не регистр MXSCR. Поэтому польза от инлайнинга несколько снижается.