簡體   English   中英

C ++,如何優化浮點算術運算?

[英]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)設置為#INFhttp : #INF 。)

  1. 有人會在乎解釋這種“效果”是如何實現的嗎?
  2. 這是“功能”嗎? 還是我可以認為這是“意外行為”,而不是簡單的“令人驚訝”?

謝謝

只是一個猜測,但是:可能是平均值(2)是直接在浮點寄存器中計算的,該寄存器的寬度為80位,並且溢出時間比64位存儲晚,導致內存翻倍。 您應該檢查反匯編的代碼,以查看是否確實如此。

這是一種功能,或者至少是故意的。 基本上,x86上的浮點寄存器比雙精度寄存器具有更高的精度和范圍(15位指數,而不是11,64位矩陣,而不是52)。 C ++標准允許對中間值使用更高的精度和范圍,並且在某些情況下,幾乎所有適用於Intel的編譯器都將這樣做。 性能差異很大。 是否獲得擴展的精度取決於編譯器何時以及是否溢出到內存。 (將結果保存在命名變量中要求編譯器至少根據標准將其轉換為實際的雙精度。)我見過的最糟糕的情況是一些代碼基本上可以做到:

return p1->average() < p2->average()

,並使用average()進行數據內部表的預期操作。 在某些情況下, p1p2實際上會指向相同的元素,但是返回值仍然為true; 函數調用的結果將溢出到內存中(並被截斷為double ),另一個函數的結果保留在浮點寄存器中。

(該函數被用作sort的排序函數,結果代碼崩潰,因為由於這種影響,它沒有定義足夠嚴格的排序標准,超出范圍的sort代碼也傳遞給了它。)

在某些情況下,編譯器可以使用比聲明的類型所隱含的類型更廣泛的類型,但是AFAIK並不是這種情況之一。

因此,我認為我們具有類似於Gcc錯誤323的效果,該錯誤在不應該使用時會使用附加精度。

x86具有80位FP內部寄存器。 盡管gcc傾向於以最高的精度使用它們(因此出現錯誤323),但我的理解是MSVC將精度設置為53位,而64位之一是精度的兩倍。 但是延長的有效位數並不是80位FP的唯一區別,指數范圍也有所增加。 和IIRC一樣,x86中沒有設置會強制使用64位double的范圍。

鍵盤現在似乎無法訪問,或者我將在沒有80位長的double的體系結構上測試您的代碼。

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM