为什么MinGW-w64浮点精度依赖于winpthreads版本?

问题描述 投票:0回答:3

我使用 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/。作为讨论线程的结果,我的解决方案是链接目标文件

CRT_fp8.o
.

将编译命令修改为

g++ -o Test.exe Test.cpp -O2 c:\mingw-w64-10.2\mingw64\x86_64-w64-mingw32\lib\CRT_fp8.o
(路径可能需要调整)导致所有线程以双精度进行浮点运算。现在来自不同线程的结果将不再不同。

多年来,这对我来说一直是一个很好的解决方案。然而,几周前,在使用不同的编译器版本时,我发现链接到

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 安装并覆盖它自己的共享库,那么行为又和过去几年一样:需要链接到 CRT_fp8.o 才能在所有线程上获得双精度。

这是一个错误吗?一个 MinGW-w64 错误?还是 libwinpthreads 错误?还是特色?我的一致精度解决方法是否过时了?新的解决方法是什么?

c++ g++ pthreads precision mingw-w64
3个回答
3
投票

即使您比较

sqrt(x)
pow(x,0.5)
在一个编译器上的结果,结果也可能不同。但是编译器可能会合理地内联
sqrt
,这显然比
pow
简单。这意味着,如果您使用 GCC 10.2 进行编译,您将获得一个内联的
sqrt
,但是当您使用 10.3 运行时 DLL 运行它时。你链接到那个
pow
,所以你甚至没有比较相同的版本。

CRT_fp8.o
所做的是提供一个替代的
_fpreset
函数来将 FPU 重置为替代的默认状态——不是 80 位 MinGW 默认值,而是 64 位精度。

请注意,MinGW 是将 GCC 硬塞进 Windows 的一次令人印象深刻的尝试。但 GCC 的起源很大程度上是一个 Stallman 项目,具有很强的 Unix 假设。

最后,最好通过迁移到 x64 来完全避免这个问题。这应该对所有 FP 数学使用 SSE2。由于 SSE2 永远不会是 80 位,所以你总是得到 64 位。


1
投票

所描述的行为是一个 MinGW-w64 错误。与原帖描述不同,与g++版本无关,只与MinGW-w64版本有关。已在此处报告:https://sourceforge.net/p/mingw-w64/bugs/897/.

那是什么意思?

使用 mingw-builds,可以在构建编译器时指定 MinGW 版本。版本 7 是安全的,版本 8 引入了所描述的错误。所以版本 7 可以用作解决方法。


0
投票

我相信 sqrt(x) 和 pow(x, 0.5) 不是完全相同的函数。应该是,但我猜 pow(x, 0.5) 在所有计算中都使用对数。 因此可能会引入小错误。除此之外,十进制值 0.5 可能会根据 IEEE 754 转换为其二进制表示形式,并且可能与 0.5 的值不完全相同。

在使用这些功能时不要记住所有这些陷阱,但它们很难跟踪。 问候 Sv B

© www.soinside.com 2019 - 2024. All rights reserved.