2011-02-15 37 views
11

我和我的博士學位。學生在物理數據分析上下文中遇到了一個問題,我可以使用一些見解。我們的代碼可以分析來自LHC實驗的數據,從而得出無法重現的結果。特別是,從獲得的計算結果相同,二進制,運行於相同機器可以在連續執行之間有所不同。我們知道許多不可重現性的不同來源,但排除了通常的嫌疑犯。浮點比較不可重現性

當比較名義上具有相同值的兩個數字時,我們已經將問題追溯到(雙精度)浮點比較操作的不可重複性。這可能會因分析中的先前步驟而偶爾發生。我們剛剛找到一個例子來測試數字是否小於0.3(請注意,我們從不測試浮點值之間的相等性)。事實證明,由於檢測器的幾何形狀,計算可能偶爾產生精確爲0.3的結果(或其最接近的雙精度表示)。

我們很清楚比較浮點數的缺陷以及FPU中超額精度可能影響比較結果的可能性。我想回答的問題是「爲什麼結果不可重現?」是否因爲FPU寄存器加載或其他FPU指令未清除過多的位,因此以前計算中的「剩餘」位會影響結果? (這看起來不太可能)我在另一個論壇上看到了一個建議,即進程在進程或線程之間切換也會由於FPU的內容存儲在堆棧中而導致浮點比較結果發生變化,從而被截斷。對這些=或其他可能的解釋的任何意見將不勝感激。

+0

請問您可以添加一個關於上下文切換建議的參考嗎?雖然我可以描繪處理器移動累加器數據並丟棄位,但這種機制對我來說似乎不是一個好的解釋,而更多細節可能會很有趣。 – 2011-02-15 19:42:23

+0

也許使用不同的編譯器優化標誌可能會解決這個問題。 – tkerwin 2011-02-15 19:42:52

+2

@火星上的咖啡:這是我的建議,所以我想我可以解釋:)問題是,FPU可以在寄存器中使用更多的位,在一些最新的處理器中可以使用多達80位雙打。現在,在單線程環境中,FPU將能夠以這種精度執行所有操作,並且您將得到一個結果。如果將其他線程/進程添加到混合中,則當OS執行上下文切換時,必須將64位雙精度寄存器的值存儲爲64位雙精度值。 – 2011-02-15 19:52:31

回答

6

猜測,發生的事情是您的計算通常在FPU內部執行一些額外的精度位,並且只在特定點處舍入(例如,當您將結果賦值給某個值時)。

當有上下文切換,但是,FPU的狀態必須被保存和恢復 - 而且有至少一個非常公平的機會,這些額外的比特被保存,並在上下文切換恢復。當它發生時,這可能不會引起重大變化,但是如果(例如)稍後從每個變量中減去一個固定值並乘以剩下的值,則差異也會相乘。

要清楚:我懷疑「遺留」位是罪魁禍首。相反,它將會損失額外的位,導致在計算中略微不同的點上四捨五入。

1

CPU的內部FPU可以存儲比double或float更高精度的浮點數。只要寄存器中的值必須存儲在其他地方,包括內存被換出到緩存中(這是我知道的事實),並且該內核上的上下文切換或操作系統中斷聽起來像是另一個簡單的源代碼,則必須轉換這些值。當然,操作系統中斷或上下文切換的時間或非熱點內存的交換是完全不可預知的,並且不可控制。

當然,這取決於平臺,但是您的描述聽起來像是在現代桌面或服務器上運行(如此x86)。

2

我在此:

#include <stdio.h> 
#include <stdlib.h> 

typedef long double ldbl; 

ldbl x[1<<20]; 

void hexdump(void* p, int N) { 
    for(int i=0; i<N; i++) printf("%02X", ((unsigned char*)p)[i]); 
} 

int main(int argc, char** argv) { 

    printf("sizeof(long double)=%i\n", sizeof(ldbl)); 

    if(argc<2) return 1; 

    int i; 
    ldbl a = ldbl(1)/atoi(argv[1]); 

    for(i=0; i<sizeof(x)/sizeof(x[0]); i++) x[i]=a; 

    while(1) { 
    for(i=0; i<sizeof(x)/sizeof(x[0]); i++) if(x[i]!=a) { 
     hexdump(&a, sizeof(a)); 
     printf(" "); 
     hexdump(&x[i], sizeof(x[i])); 
     printf("\n"); 
    } 
    } 

} 

使用/ Qlong_double編譯IntelC,使其產生這樣的:

;;;  for(i=0; i<sizeof(x)/sizeof(x[0]); i++) if(x[i]!=a) { 

     xor  ebx, ebx          ;25.10 
           ; LOE ebx f1 
.B1.9:       ; Preds .B1.19 .B1.8 
     mov  esi, ebx          ;25.47 
     shl  esi, 4          ;25.47 
     fld  TBYTE PTR [[email protected]@3PA_TA+esi]     ;25.51 
     fucomp             ;25.57 
     fnstsw ax           ;25.57 
     sahf             ;25.57 
     jp  .B1.10  ; Prob 0%      ;25.57 
     je  .B1.19  ; Prob 79%      ;25.57 
[...] 
.B1.19:       ; Preds .B1.18 .B1.9 
     inc  ebx           ;25.41 
     cmp  ebx, 1048576         ;25.17 
     jb  .B1.9   ; Prob 82%      ;25.17 

,並開始10個實例具有不同的 「種子」。正如你所看到的,它將來自內存的10個字節長的雙精度浮點數與FPU堆棧中的一個進行比較,所以在 操作系統不能保持完全精度的情況下,我們肯定會看到一個錯誤。 嗯,它們仍然運行時沒有檢測到任何東西......這真的不是真的 令人驚訝的,因爲x86有一次保存/恢復整個FPU狀態的命令,無論如何一個操作系統不會保持完全精度完全破碎。

因此,無論它的一些獨特的OS/CPU /編譯器,或不同的比較結果 在節目中,例如,在程序改變的東西,重新編譯它,或者它的 錯誤後產生的。緩衝區溢出。

2

程序是多線程的嗎?

如果是的話,我會懷疑競賽狀況。

如果不是,程序執行是確定性的。在給定相同輸入的情況下獲得不同結果的最可能的原因是未定義的行爲,即程序中的錯誤。讀取未初始化的變量,陳舊的指針,覆蓋堆棧中某些FP編號的最低位等。可能性無窮無盡。如果你在linux上運行這個,嘗試在valgrind下運行它,看它是否發現了一些錯誤。

順便說一句,你是如何將問題縮小到FP比較的?

(?遠投:硬件故障例如,失效RAM芯片可能會導致數據以不同的方式在不同的場合閱讀雖然,這可能會崩潰操作系統相當快。)

任何其他的解釋是不合理的 - - 操作系統或硬件中的錯誤不會久久不見。

0

我只是合併了David Rodriguez和Bo Persson的一些評論,並做了一個猜測。

使用SSE3指令時,它可能是任務切換嗎?基於此Intel article on using SSE3 instructions保留寄存器狀態FSAVE和FRESTOR的命令已被FXSAVE和FXRESTOR所取代,該寄存器應該處理累加器的全長 。

在x64機器上,我想「不正確的」指令可能包含在一些外部編譯庫中。

0

你肯定會碰到GCC Bug n°323,這是由於FPU的過度精確度造成的。

解決方案:

  • 使用SSE(或AVX,這是2016 ......)來執行你的計算
  • 使用-ffloat-store編譯開關。來自GCC文檔。

不要儲存在寄存器浮點變量,並且抑制可能會改變是否浮點值從寄存器或存儲器所採取的其他選項。
這個選項可以防止68000機器上的浮動寄存器(68881的浮動寄存器)保持比雙倍應該具有的精度更高的精度。 類似的x86架構。 對於大多數程序來說,多餘的精度確實不錯,但少數程序依賴於IEEE浮點的精確定義。 在對這些程序進行修改以將所有相關的中間計算存儲到變量中後,使用-ffloat-store來存儲這些程序。