繁体   English   中英

为什么这个计算在boost :: thread和std :: thread中给出不同的结果?

[英]Why does this calculation give different result in boost::thread and std::thread?

当在boost::thread执行此浮点计算时,它会提供与在std::thread或主线程中执行时不同的结果。

void print_number()
{
    double a = 5.66;
    double b = 0.0000001;
    double c = 500.4444;
    double d = 0.13423;
    double v = std::sin(d) * std::exp(0.4 * a + b) / std::pow(c, 2.3);

    printf("%llX\n%0.25f\n", *reinterpret_cast<unsigned long long*>(&v), v);
}

这似乎是因为boost::thread默认使用53位内部精度进行浮点数学运算,而主线程使用64位精度。 如果在创建boost::thread之后使用_fpreset()重置FPU单元的状态,则结果与主线程中的结果相同。

我正在使用Embarcadero C ++ Builder 10.1(编译器bcc32c版本3.3.1)和Boost 1.55.0。 我的环境是Windows 7,我正在构建32位Windows目标。

工作范例:

#include <tchar.h>
#include <thread>
#include <boost/thread.hpp>
#include <cstdio>
#include <cmath>
#include <cfloat>

namespace boost { void tss_cleanup_implemented() {} }

void print_number()
{
    double a = 5.66;
    double b = 0.0000001;
    double c = 500.4444;
    double d = 0.13423;
    double v = std::sin(d) * std::exp(0.4 * a + b) / std::pow(c, 2.3);

    // Edit:
    // Avoiding the undefined behaviour by a reinterpret_cast, as
    // mentioned in some answers and comments.
    unsigned long long x;
    memcpy(&x, &v, sizeof(x));

    printf("%llX\n%0.25f\n", x, v);
}

void print_number_2()
{
    // Reset FPU precision to default
    _fpreset();
    print_number();
}

int _tmain(int argc, _TCHAR* argv[])
{
    print_number();

    std::thread t1(&print_number);
    t1.join();

    boost::thread t2(&print_number);
    t2.join();

    boost::thread t3(&print_number_2);
    t3.join();

    getchar();
    return 0;
}

输出:

3EAABB3194A6E99A
0.0000007966525939409087744
3EAABB3194A6E99A
0.0000007966525939409087744
3EAABB3194A6E999
0.0000007966525939409087488
3EAABB3194A6E99A
0.0000007966525939409087744

题:

  • 为什么会这样? 是不是一个新的线程应该从父线程继承浮点环境?
  • 这是编译器或Boost中的错误,还是我的期望错了?

这: *reinterpret_cast<unsigned long long*>(&v)是未定义的行为,因为v不是unsigned_long_long 如果要将double的二进制表示复制到整数类型,请使用memcpy() 请注意,即使使用memcpy() ,它的实现也定义了二进制表示的外观,但您可以保证可以“加载已保存的内容”。 没有更多的AFAIK。

这不是64位和53位精度FPU计算之间的差异,它与ROUNDING有所不同。 两个结果之间的唯一区别在于答案的最低点。 看起来boost的线程启动代码没有正确初始化FPU标志,并且默认的舍入模式是down或chop,而不是最近的。

如果是这种情况,那么它可能是boost :: thread中的一个错误。 如果另一个库正在更改FPU标志(通过_controlfp_s或类似的函数),或者如果新线程是线程池的一部分,则该线程的先前用户更改了标志,并且池未重置他们在重用线程之前。

差别似乎是std::thread实现了一个_fpreset() ,而boost::thread显然没有。 如果你改变了这条线

namespace boost { void tss_cleanup_implemented() { } }

为了清晰起见,格式化了一点:

namespace boost 
{ 
    void tss_cleanup_implemented() 
    { 
        _fpreset(); 
    }
}

您将看到所有值现在完全相同( 3EAABB3194A6E99A )。 这告诉我Boost没有做_fpreset() 此调用是必要的,因为某些Windows API调用会破坏C ++ Builder(32位)使用的标准FPU设置,并且不会将它们设置回原来的状态(这也是您在Delphi中可能遇到的问题)。

std::threadboost:thread使用Win32 API调用来处理线程。

有些东西告诉我你已经预料到了这一点,因此使用print_number_2()进行测试,它执行_fpreset()

要发白,你需要一个更好的编译器。


这似乎是因为boost :: thread默认使用53位内部精度进行浮点数学运算,而主线程使用64位精度。 如果在创建boost :: thread之后使用_fpreset()重置FPU单元的状态,则结果与主线程中的结果相同。

疯了吧。 如果您的编译器对不同的代码区域使用不同的FP单元(即x87与SSE),则应该使用您可以找到的最大火力来刻录该编译器。

在Linux Mint 17.3上的g ++ - 6.1和clang ++ - 3.8下运行此代码,为每种线程类型提供相同的结果。

#include <thread>
#include <boost/thread.hpp>
#include <cstdio>
#include <cmath>

void print_number() {
    double a = 5.66;
    double b = 0.0000001;
    double c = 500.4444;
    double d = 0.13423;
    double v = std::sin(d) * std::exp(0.4 * a + b) / std::pow(c, 2.3);

    printf("%llX\n%0.25f\n", *reinterpret_cast<unsigned long long*>(&v), v);
}

int main() {
    print_number();

    std::thread t1(&print_number);
    t1.join();

    boost::thread t2(&print_number);
    t2.join();
}

CXX -std = c ++ 14 -O3 -c test test.c -pthread -lboost_thread -lboost_system

3EAABB3194A6E999
0.0000007966525939409086685

3EAABB3194A6E999
0.0000007966525939409086685

3EAABB3194A6E999
0.0000007966525939409086685


正如@lorro在他/她的回答中指出的那样,你正在打破reinterpret_cast的别名规则。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM