为什么 MinGW GCC 对 atan2、cos、exp 和 sin 使用 x87 80 位 FP 库代码?

Mar*_*own 5 floating-point gcc mingw avx x87

我在从 Intel 2023 和 MSC Visual C++ 2022 移植工作数字代码时遇到一个奇怪的问题。使用 GCC 编译的代码非常准确(太准确),因为某些库调用以完整的 80 位浮点精度工作 - 特别是 sqrt、sin 和 cos。我可以通过使用 TUI 跟踪 gdb 的库调用来反汇编库代码执行来验证这一点。

它也出现在基准计时中,因为 x87 atan2、cos、exp 和 sin 都约为 100 个周期,而 sqrt 约为 80 个周期。SSE/AVX2 代码的相应时序低于 50 个周期,大部分在 20-30 个周期左右。

奇怪的是 tan、atan是使用 AVX2 编译的。但 cos、sin、sqrt 和 atan2 在 GCC 系统库中使用旧版 x87 代码。我已经在 32 位端口和 64 位版本上尝试过此操作,并且都遇到了相同的问题。我是海湾合作委员会的新手,所以我可能忽略了一些事情。我在 Windows 上使用默认的 MinGW 端口版本 13.1.0 (MinGW-W64 i686-ucrt-posix-dwarf),它可能有其自身的特点。

顺便说一句,我刚刚注意到 MSC 2022 有时会编码 x87 sqrt,即使启用了所有 gofaster 优化和 AVX2 代码,因为这也是我之前没有注意到的基准计时中的异常值。Intel 将其编译为本机 sqrtsd,因此速度要快得多。我回到 MSC x86 进行内联汇编,以确认 x87 trig 指令的基准计时。

编译器选项有:
gcc -c -O3 -Ofast -march=native -mavx2 -mfpmath=sse benchmark.cpp

链接正在采用 GCC 默认系统库,这似乎是问题所在 - 我的代码或任何内联生成 SSE 或 AVX2 代码的系统代码,以便 tan 和 atan 都可以,但任何生成库调用的都正在执行 x87 指令全 80 位精度的超越函数。我认为这可能与我能找到的最接近的线程有关:

为什么 MinGW-w64 浮点精度取决于 winpthreads 版本?

我想强制它使用完全 AVX2 或 SSE2 代码的不同 FP 库,或者使用 和 之类的东西重新编译现有的-march=native -mavx2库-Ofast。在这里,速度比精确的标准合规性更重要。这涉及到很多三角函数。

我完全有可能在 GCC 中链接了错误的默认库。我有一种基于合并 BSD trig 库函数源代码的解决方法,但这并不优雅。

如果有人想在他们的系统上尝试一下,我可以发布一个基准测试代码示例,但它会比这里正常的情况要长一些。我希望有人已经知道答案......

这些是使用 AVX2 代码生成 MSC 2022 与 GCC 13.0.1 进行原始三角运算的机器周期基准。它在 Intel i5-12600 上运行,对两个编译器都进行了最大优化。

MSC 海湾合作委员会
晒黑 12 13
阿坦2 27 122
日志 11 76
经验值 11 136
罪 14 115
因斯 13 117
正科斯 19 127
晒黑 18 20

与应有的水平相比,使用 x87 代码的代码脱颖而出了约 100 个周期。

我想在编译时使用正确的浮点库获取代码-O3 -Ofast -mavx2

这是显示我的问题的最小示例代码以及 GDB 中反汇编的快照,其中显示了 sin 如何变成 x87 FSIN 等。测试您是否遇到类似问题的另一种方法是对 sin(x) 和 tan(x) 进行基准测试如果 tan 计时约为 20 个周期,sin 约为 40,那么你最喜欢的分析器就可以了(tan 比 sin 快 2 倍)。任何三角函数都需要 100+ 个周期,并且是缓慢的 x87 代码。

#include "stdio.h"
#include "math.h"

// Toy use of sin & tan to see if they compile using SSE2 or x87

int main(int argc, char* argv[]){
double x, y;
if (argc>1) x = atof(argv[1]); else x = 3.1415926535/2;
y = sin(x);
printf("sin of %g is %18.10g\n", x, y);
x = x/2;
y = tan(x);
printf("tan of %g is %18.10g\n", x, y);
}
Run Code Online (Sandbox Code Playgroud)

使用 GDB 中的 x87 代码反汇编 sin 例程(tan 即可)

 <__sinl_internal>        fldt   (%rdx)                                                                
 <__sinl_internal+2>      fsin                                                                       
 <__sinl_internal+4>      fnstsw %ax                                                                
 <__sinl_internal+6>      test   $0x400,%eax                                                        
 <__sinl_internal+11>     jne    0x7ff656332e6b                              
 <__sinl_internal+13>     mov    %rcx,%rax                                                          
 <__sinl_internal+16>     movq   $0x0,0x8(%rcx)                                                     
 <__sinl_internal+24>     fstpt  (%rcx)                                                             
 <__sinl_internal+26>     ret              
Run Code Online (Sandbox Code Playgroud)

我现在相当确信 @emacsdrivesmenuts 是对的,我必须在 Mingw 下使用正确的 FP 优化来重建默认的系统数学库来解决这个问题,但我不知道该怎么做!

感谢您的任何启发!