为什么 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 优化来重建默认的系统数学库来解决这个问题,但我不知道该怎么做!

感谢您的任何启发!