[英]C++, How floating-point arithmetic operations get optimized?
在x86架構上的極限情況下測試簡單的算術運算時,我觀察到了令人驚訝的行為:
const double max = 9.9e307; // Near std::numeric_limits<double>::max()
const double init[] = { max, max, max };
const valarray<double> myvalarray(init, 3);
const double mysum = myvalarray.sum();
cout << "Sum is " << mysum << endl; // Sum is 1.#INF
const double myavg1 = mysum/myvalarray.size();
cout << "Average (1) is " << myavg1 << endl; // Average (1) is 1.#INF
const double myavg2 = myvalarray.sum()/myvalarray.size();
cout << "Average (2) is " << myavg2 << endl; // Average (2) is 9.9e+307
(已在發布模式下通過MSVC進行測試,以及通過Codepad.org通過gcc進行了測試。MSVC的調試模式將平均值(2)設置為#INF
。)
我期望平均值(2)等於平均值(1),但在我看來C ++內置除法運算符已由編譯器優化,因此以某種方式阻止了累加達到#INF
。
簡而言之:大數的平均值不會產生#INF
。
我在MSVC上使用std算法觀察到了相同的行為:
const double mysum = accumulate(init, init+3, 0.);
cout << "Sum is " << mysum << endl; // Sum is 1.#INF
const double myavg1 = mysum/static_cast<size_t>(3);
cout << "Average (1) is " << myavg1 << endl; // Average (1) is 1.#INF
const double myavg2 = accumulate(init, init+3, 0.)/static_cast<size_t>(3);
cout << "Average (2) is " << myavg2 << endl; // Average (2) is 9.9e+307
(但是這次,gcc將平均值(2)設置為#INF
: http : #INF
。)
謝謝
只是一個猜測,但是:可能是平均值(2)是直接在浮點寄存器中計算的,該寄存器的寬度為80位,並且溢出時間比64位存儲晚,導致內存翻倍。 您應該檢查反匯編的代碼,以查看是否確實如此。
這是一種功能,或者至少是故意的。 基本上,x86上的浮點寄存器比雙精度寄存器具有更高的精度和范圍(15位指數,而不是11,64位矩陣,而不是52)。 C ++標准允許對中間值使用更高的精度和范圍,並且在某些情況下,幾乎所有適用於Intel的編譯器都將這樣做。 性能差異很大。 是否獲得擴展的精度取決於編譯器何時以及是否溢出到內存。 (將結果保存在命名變量中要求編譯器至少根據標准將其轉換為實際的雙精度。)我見過的最糟糕的情況是一些代碼基本上可以做到:
return p1->average() < p2->average()
,並使用average()
進行數據內部表的預期操作。 在某些情況下, p1
和p2
實際上會指向相同的元素,但是返回值仍然為true; 函數調用的結果將溢出到內存中(並被截斷為double
),另一個函數的結果保留在浮點寄存器中。
(該函數被用作sort
的排序函數,結果代碼崩潰,因為由於這種影響,它沒有定義足夠嚴格的排序標准,超出范圍的sort
代碼也傳遞給了它。)
g++ -O0 -g -S test.cpp -o test.s0
g++ -O3 -g -S test.cpp -o test.s3
比較test.s [03]顯示,實際上甚至沒有再次調用valarray :: sum。 我已經使用了很長時間,但以下片段似乎是定義片段:
.loc 3 16 0 ; test.s0
leal -72(%ebp), %eax
movl %eax, (%esp)
call __ZNKSt8valarrayIdE3sumEv
fstpl -96(%ebp)
leal -72(%ebp), %eax
movl %eax, (%esp)
call __ZNKSt8valarrayIdE4sizeEv
movl $0, %edx
pushl %edx
pushl %eax
fildq (%esp)
leal 8(%esp), %esp
fdivrl -96(%ebp)
fstpl -24(%ebp)
.loc 3 17 0
與
.loc 1 16 0 ; test.s3
faddl 16(%eax)
fdivs LC3
fstpl -336(%ebp)
LVL6:
LBB449:
LBB450:
.loc 4 514 0
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.