![](/img/trans.png)
[英]Boost thread, Posix thread and STD thread, why do they offer different performances?
[英]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
這: *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::thread
和boost: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.00000079665259394090866853EAABB3194A6E999
0.00000079665259394090866853EAABB3194A6E999
0.0000007966525939409086685
正如@lorro在他/她的回答中指出的那樣,你正在打破reinterpret_cast
的別名規則。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.