2009-06-09 59 views
6

已經閱讀這question我相當肯定,一個給定的過程使用浮點算術相同的輸入(在相同的硬件,編譯相同的編譯器)應該是確定性。我正在審查一個不是這樣的案例,並試圖確定可能造成這種情況的原因。什麼可能導致一個確定性的過程產生浮點錯誤

我編譯了一個可執行文件,我給它提供了完全相同的數據,運行在一臺機器上(非多線程),但我得到的錯誤約爲3.814697265625e-06,仔細搜索後我發現實際上等於1/4^9 = 1/2^18 = 1/262144。這非常接近32位浮點數的精確度(根據維基百科約7位數)

我懷疑它與應用於代碼的優化有關。我使用的是intel C++編譯器,並且已經將浮點運算變爲快速而不是安全或嚴格。這可能會使浮點進程不確定嗎?是否有其他優化等可能導致此行爲?

編輯:由於每人的建議我重新編譯浮點炒作的代碼變得安全,現在我得到穩定的結果。這使我可以澄清這個問題 - 浮點推測實際上做了什麼,以及如何在相同的輸入應用於同一個二進制文件(即一次編譯,多次運行)時生成不同的結果?

@Ben我正在使用英特爾(R)C++ 11.0.061 [IA-32]進行編譯,並且正在使用英特爾四核處理器。

+0

什麼處理器處理器和什麼編譯器? .. – Ben 2009-06-09 06:34:50

+0

如果你已經找出哪個標誌導致它,爲什麼不檢查編譯器文檔? – 2009-06-09 06:51:24

回答

13

幾乎在任何有快速模式和安全模式的情況下,您都會發現某種折衷。否則一切都會以快速安全模式運行:-)。不管你有多相信(儘管有經驗證據),你的過程是而不是確定性的。

我會說你的解釋是最有可能的。把它放在安全模式下,看看非確定性是否消失。那肯定會告訴你。

至於是否還有其他優化,如果您使用相同的編譯器/鏈接器在相同的硬件上編譯,並且與這些工具具有相同的選項,它應該生成相同的代碼。除了快速模式(或由於宇宙射線造成的內存中的位元腐爛,除此之外不太可能),我看不到任何其他可能性。

追隨你的更新:

英特爾具有文檔here其中介紹了一些他們不允許在安全模式下做的事情,包括但不限於:

  • 重新關聯: (a+b)+c -> a+(b+c)
  • 零折:x + 0 -> x, x * 0 -> 0
  • 倒數乘積:a/b -> a*(1/b)

儘管您聲明這些操作是編譯時定義的,但英特爾芯片卻非常聰明。他們可以重新排列指令,以保持管線充滿多CPU設置,除非代碼明確禁止這種行爲,否則在運行時(不是編譯時),事情可能會發生變化,以保持全速運行。

這在第15頁中有關矢量化的討論(「問題:在同一處理器上的相同數據上重新運行相同的二進制文件的不同結果」)。

我的建議是決定是否需要原始的咕嚕聲或結果的可重複性,然後根據它選擇模式。

0

如果您的程序是並行化的,因爲它可能運行在四核上,那麼它可能是非確定性的。

想象一下,您有4個處理器將浮點值添加到相同的內存位置。然後,你可能會得到

(((InitialValue+P1fp)+P2fp)+P3fp)+P4fp 

(((InitialValue+P2fp)+P3fp)+P1fp)+P4fp 

或任何其他可能的排序中。

哎呀,你甚至可能得到

InitialValue+(P2fp+P3fp)+(P1fp+P4fp) 

如果編譯器是不夠好。

不幸的是,浮點加法不是交換或關聯的。實數算術是,但浮點數不是,因爲四捨五入,溢出和下溢。

因此,並行FP計算通常是非確定性的。 「經常」,因爲那看起來像

on each processor 
    while(there is work to do) { 
     get work 
     calculate result 
     add to total 
    } 

方案將是不確定的,因爲時間的關係,每個花費可能有很大的不同量 - 你無法預測操作的順序。 (更糟糕的是,如果線程交互。)

但並非總是如此,因爲有確定性的並行編程風格。

當然,許多關心決定論的人都在整數或定點工作以避免這個問題。我特別喜歡超級累加器,512,1024或2048位數,可以添加浮點數,而不會遭受舍入誤差。


至於單線程應用程序:編譯器可能會重新排列代碼。不同的彙編可能會給出不同的答案。但是任何特定的二進制都應該是確定性的

除非......您正在使用動態語言。這會執行最優化,重新排序FP計算,這些計算隨時間而變化。

或者除非......真正的遠景:安騰具有一些功能,如ALAT,甚至可以實現單線程編碼的非確定性功能。你不可能受到這些影響。

相關問題