为什么 MinGW-w64 浮点精度取决于 winpthreads 版本?
Why does MinGW-w64 floating-point precision depend on winpthreads version?
我使用 MinGW-w64 g++ 编译器 10.2 和 10.3。我自己使用 https://github.com/niXman/mingw-builds.
构建了两者
Windows 上的 g++ 有一个奇怪之处:应用程序的主线程将以双精度执行浮点运算,但其他线程将以扩展精度执行它们。这可以用这个小程序重现 ("Test.cpp"):
#include <iostream>
#include <future>
#include <cmath>
#include <string>
#include <thread>
std::string getResult()
{
std::cout << "Thread id: " << std::this_thread::get_id() << std::endl;
std::string result = "Result:\n";
double a, b, c;
int i;
for (i = 0, a = 1.0; i < 10000000; i++)
{
a *= 1.00000001;
b = sqrt(a);
c = pow(a, 0.5);
if (std::abs(b - c) < 1.0e-50)
continue;
result += std::string("a: ") + std::to_string(a) + " - Numbers differ.\n";
}
return result;
}
int main()
{
std::string string1 = getResult();
std::future<std::string> result = std::async(std::launch::async, getResult);
std::string string2 = result.get();
if (string1 != string2)
{
std::cout << "The results are different." << std::endl;
}
else
{
std::cout << "The results are the same." << std::endl;
}
return 0;
}
像这样使用优化 (!) 编译它时:
g++ -o Test.exe Test.cpp -O2
并执行它,输出是:
C:\...Path...>Test.exe
Thread id: 1
Thread id: 2
The results are different.
对我来说这是个大问题。出于安全原因,我希望所有数值结果始终相同——无论它们是在不同线程上异步执行还是在主线程上顺序执行。否则,例如我的单元测试可能会失败,具体取决于执行条件。
我已经向 MinGW-w64 邮件列表发布了一条消息:https://sourceforge.net/p/mingw-w64/mailman/message/34896011/。作为讨论线程的结果,我的解决方案是 link 目标文件 CRT_fp8.o
.
将编译命令修改为g++ -o Test.exe Test.cpp -O2 c:\mingw-w64-10.2\mingw64\x86_64-w64-mingw32\lib\CRT_fp8.o
(路径可能需要调整)导致所有线程以双精度进行浮点运算。现在来自不同线程的结果将不再不同。
多年来,这对我来说一直是一个很好的解决方案。然而,几周前,在使用不同的编译器版本时,我发现 linking 到 CRT_fp8.o
的解决方案并不像我预期的那样稳定。
当使用 g++ 10.2 编译然后更改路径以包含 g++ 10.3 的“bin”文件夹时,线程将再次产生不同的结果。我可以在此处使用这些控制台命令重现它:
set path=c:\mingw-w64-10.2\mingw64\bin
g++ -o Test.exe Test.cpp -O2 c:\mingw-w64-10.2\mingw64\x86_64-w64-mingw32\lib\CRT_fp8.o
C:\...Path...>Test.exe
Thread id: 1
Thread id: 2
The results are the same.
set path=c:\mingw-w64-10.3\mingw64\bin
C:\...Path...>Test.exe
Thread id: 1
Thread id: 2
The results are different.
这又是真的糟透了!如果我的应用程序的用户偶然在他的路径中有错误的库,他将得到不同的结果! :-(
另一个令人惊讶的是,当仅使用 g++ 10.3 时,CRT_fp8.o
的解决方法似乎是不必要的:
set path=c:\mingw-w64-10.3\mingw64\bin
g++ -o Test.exe Test.cpp -O2
C:\...Path...>Test.exe
Thread id: 1
Thread id: 2
The results are the same.
我使用了 MinGW-w64“bin”文件夹中的共享库,发现该行为取决于文件“libwinpthread-1.dll”。如果我将文件从 g++ 10.2 安装复制到 g++ 10.3 安装覆盖它自己的共享库,那么行为又像过去几年一样:需要 linking 到 CRT_fp8.o 才能获得所有线程上的双精度。
这是一个错误吗?一个 MinGW-w64 错误?还是 libwinpthreads 错误?还是特色?我的一致精度解决方法是否过时了?新的解决方法是什么?
即使您在一个编译器上比较 sqrt(x)
和 pow(x,0.5)
的结果,结果也可能不同。但是编译器可能会合理地内联 sqrt
,这显然比 pow
简单。这意味着如果你用 GCC 10.2 编译你会得到一个内联的 sqrt
但是当你然后 运行 它与一个 10.3 运行time DLL。你 link 和那个 pow
,所以你甚至没有比较相同的版本。
CRT_fp8.o
所做的是提供一个替代 _fpreset
函数来将 FPU 重置为替代默认状态 - 不是 80 位 MinGW 默认值,而是 64 位精度。
请注意,MinGW 是将 GCC 硬塞进 Windows 的一次令人印象深刻的尝试。但是 GCC 的起源很大程度上是一个 Stallman 项目,具有很强的 Unix 假设。
最后,最好通过迁移到 x64 来完全避免该问题。这应该对所有 FP 数学使用 SSE2。由于 SSE2 永远不会是 80 位,您总是会得到 64 位。
所描述的行为是一个 MinGW-w64 错误。与原文post中的描述不同,与g++版本无关,只与MinGW-w64版本有关。已在此处报告:https://sourceforge.net/p/mingw-w64/bugs/897/.
这是什么意思?
使用 mingw-builds,可以在构建编译器时指定 MinGW 版本。版本 7 是安全的,版本 8 引入了所描述的错误。所以版本 7 可以用作解决方法。
我使用 MinGW-w64 g++ 编译器 10.2 和 10.3。我自己使用 https://github.com/niXman/mingw-builds.
构建了两者Windows 上的 g++ 有一个奇怪之处:应用程序的主线程将以双精度执行浮点运算,但其他线程将以扩展精度执行它们。这可以用这个小程序重现 ("Test.cpp"):
#include <iostream>
#include <future>
#include <cmath>
#include <string>
#include <thread>
std::string getResult()
{
std::cout << "Thread id: " << std::this_thread::get_id() << std::endl;
std::string result = "Result:\n";
double a, b, c;
int i;
for (i = 0, a = 1.0; i < 10000000; i++)
{
a *= 1.00000001;
b = sqrt(a);
c = pow(a, 0.5);
if (std::abs(b - c) < 1.0e-50)
continue;
result += std::string("a: ") + std::to_string(a) + " - Numbers differ.\n";
}
return result;
}
int main()
{
std::string string1 = getResult();
std::future<std::string> result = std::async(std::launch::async, getResult);
std::string string2 = result.get();
if (string1 != string2)
{
std::cout << "The results are different." << std::endl;
}
else
{
std::cout << "The results are the same." << std::endl;
}
return 0;
}
像这样使用优化 (!) 编译它时:
g++ -o Test.exe Test.cpp -O2
并执行它,输出是:
C:\...Path...>Test.exe
Thread id: 1
Thread id: 2
The results are different.
对我来说这是个大问题。出于安全原因,我希望所有数值结果始终相同——无论它们是在不同线程上异步执行还是在主线程上顺序执行。否则,例如我的单元测试可能会失败,具体取决于执行条件。
我已经向 MinGW-w64 邮件列表发布了一条消息:https://sourceforge.net/p/mingw-w64/mailman/message/34896011/。作为讨论线程的结果,我的解决方案是 link 目标文件 CRT_fp8.o
.
将编译命令修改为g++ -o Test.exe Test.cpp -O2 c:\mingw-w64-10.2\mingw64\x86_64-w64-mingw32\lib\CRT_fp8.o
(路径可能需要调整)导致所有线程以双精度进行浮点运算。现在来自不同线程的结果将不再不同。
多年来,这对我来说一直是一个很好的解决方案。然而,几周前,在使用不同的编译器版本时,我发现 linking 到 CRT_fp8.o
的解决方案并不像我预期的那样稳定。
当使用 g++ 10.2 编译然后更改路径以包含 g++ 10.3 的“bin”文件夹时,线程将再次产生不同的结果。我可以在此处使用这些控制台命令重现它:
set path=c:\mingw-w64-10.2\mingw64\bin
g++ -o Test.exe Test.cpp -O2 c:\mingw-w64-10.2\mingw64\x86_64-w64-mingw32\lib\CRT_fp8.o
C:\...Path...>Test.exe
Thread id: 1
Thread id: 2
The results are the same.
set path=c:\mingw-w64-10.3\mingw64\bin
C:\...Path...>Test.exe
Thread id: 1
Thread id: 2
The results are different.
这又是真的糟透了!如果我的应用程序的用户偶然在他的路径中有错误的库,他将得到不同的结果! :-(
另一个令人惊讶的是,当仅使用 g++ 10.3 时,CRT_fp8.o
的解决方法似乎是不必要的:
set path=c:\mingw-w64-10.3\mingw64\bin
g++ -o Test.exe Test.cpp -O2
C:\...Path...>Test.exe
Thread id: 1
Thread id: 2
The results are the same.
我使用了 MinGW-w64“bin”文件夹中的共享库,发现该行为取决于文件“libwinpthread-1.dll”。如果我将文件从 g++ 10.2 安装复制到 g++ 10.3 安装覆盖它自己的共享库,那么行为又像过去几年一样:需要 linking 到 CRT_fp8.o 才能获得所有线程上的双精度。
这是一个错误吗?一个 MinGW-w64 错误?还是 libwinpthreads 错误?还是特色?我的一致精度解决方法是否过时了?新的解决方法是什么?
即使您在一个编译器上比较 sqrt(x)
和 pow(x,0.5)
的结果,结果也可能不同。但是编译器可能会合理地内联 sqrt
,这显然比 pow
简单。这意味着如果你用 GCC 10.2 编译你会得到一个内联的 sqrt
但是当你然后 运行 它与一个 10.3 运行time DLL。你 link 和那个 pow
,所以你甚至没有比较相同的版本。
CRT_fp8.o
所做的是提供一个替代 _fpreset
函数来将 FPU 重置为替代默认状态 - 不是 80 位 MinGW 默认值,而是 64 位精度。
请注意,MinGW 是将 GCC 硬塞进 Windows 的一次令人印象深刻的尝试。但是 GCC 的起源很大程度上是一个 Stallman 项目,具有很强的 Unix 假设。
最后,最好通过迁移到 x64 来完全避免该问题。这应该对所有 FP 数学使用 SSE2。由于 SSE2 永远不会是 80 位,您总是会得到 64 位。
所描述的行为是一个 MinGW-w64 错误。与原文post中的描述不同,与g++版本无关,只与MinGW-w64版本有关。已在此处报告:https://sourceforge.net/p/mingw-w64/bugs/897/.
这是什么意思?
使用 mingw-builds,可以在构建编译器时指定 MinGW 版本。版本 7 是安全的,版本 8 引入了所描述的错误。所以版本 7 可以用作解决方法。