提问人:Martin Brown 提问时间:10/26/2023 最后编辑:Martin Brown 更新时间:10/28/2023 访问量:68
为什么 MinGW GCC 对 atan2、cos、exp 和 sin 使用 x87 80 位 FP 库代码?
Why does MinGW GCC use x87 80bit FP library code for atan2, cos, exp & sin?
问:
我在从英特尔 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 位版本上都尝试过,并且在两者中都遇到了同样的问题。我是 GCC 的新手,所以我可能忽略了一些东西。我在 Windows 上使用默认的 MinGW 端口版本 13.1.0 (MinGW-W64 i686-ucrt-posix-dwarf),它可能有自己的特点。
顺便说一句,我刚刚注意到 MSC 2022 有时会对 x87 sqrt 进行编码,即使启用了所有 gofaster 优化和 AVX2 代码,因为这也是我以前没有注意到的基准时间中的异常值。英特尔将其编译为原生 sqrtsd,因此速度非常快。我回到了 MSC x86 的内联汇编程序,以确认 x87 trig 指令的基准时间。
编译器选项包括:
gcc -c -O3 -Ofast -march=native -mavx2 -mfpmath=sse benchmark.cpp
链接采用 GCC 默认系统库的任何内容,这似乎是问题所在 - 我的代码或任何内联生成 SSE 或 AVX2 代码的系统代码,因此 tan 和 atan 没问题,但任何生成库调用的系统都以 87 位精度执行超越函数的 x80 指令。我认为它可能与我能找到的最接近的线程有关:
为什么 MinGW-w64 浮点精度取决于 winpthreads 版本?
我想强制它使用完全使用 AVX2 或 SSE2 代码的不同 FP 库,或者使用 和 之类的东西重新编译现有库。在这里,速度比精确的标准合规性更重要。这涉及到很多三角函数。-march=native -mavx2
-Ofast
我完全有可能在 GCC 中链接了错误的默认库。我有一种基于合并 BSD trig 库函数源代码的工作方法,但这并不优雅。
如果有人想在他们的系统上尝试,我可以发布基准测试的代码示例,但它会比这里看起来更长一些。我希望有人已经知道答案了......
这些是使用 AVX2 代码生成 MSC 2022 与 GCC 13.0.1 进行原始三角操作的机器周期基准。它在英特尔 i5-12600 上运行,并在两个编译器上进行了最大程度的优化。
地中海安卡 | 海湾合作委员会 | |
---|---|---|
阿坦 | 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 倍)。任何 trig 函数 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);
}
使用 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
我现在相当确信@emacsdrivesmenuts是对的,我必须在 Mingw 下使用正确的 FP 优化重建默认系统数学库来解决这个问题,但我不知道该怎么做!
感谢您的启发!
答: 暂无答案
评论
float versions:
sqrt()
sqrtsd
-fno-math-errno
-ffast-math
-Ofast
st0
libm
-O3
libm