簡體   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