[英]Float operation optimization leads to rare and strange behaviour with Visual C++
我在用C ++編寫的例程中做了幾個浮點運算,並用Visual C ++ 2008編譯。我也激活了優化(/ O2)。
C ++中的代碼看起來大致如下:
int Calculate( CalculationParams ¶ms )
{
const ConfigurationParams& configParams = ConfigReader::Instance().Parameters();
float m1 = configParams.p1 * configParams.p2;
float m2 = configParams.p3 * configParams.p4;
float m3 = configParams.p5 * configParams.p6;
....
}
ConfigReader是一個包含用於計算的參數結構的單例。 這是由configParams引用引用的。
激活優化后,在極少數情況下,我會得到計算錯誤,結果完全錯誤。
看看反匯編,我看到了這個:
int Calculate( CalculationParams ¶ms )
{
...
const ConfigurationParams& configParams = ConfigReader::Instance().Parameters();
call ConfigReader::Instance()
move ebx, eax
float m1 = configParams.p1 * configParams.p2;
fld dword ptr[ebx + 0D4h]
add ebx, 8
fmul dword ptr [ebx + 0ECh]
float m2 = configParams.p3 * configParams.p2;
float m3 = configParams.p4 * configParams.p2;
...
}
首先,我們看到它不調用Parameters()。 這是可以理解的,因為參數結構位於類中,8個字節(在兩個其他浮點之后)。 因此,在調用之后,eax具有ConfigReader CLASS的地址(而不是ConfigurationParams結構)。
然后它嘗試加載浮動。 這是問題發生的地方。 由於某種原因,對於指向ConfigReader類的ebx,加載操作的偏移量不正確。 它應首先添加8,以使偏移正確。
是否有可能編譯器假定fld操作將花費比add操作更長的時間,並且在從內存加載float之前,ebx會以某種方式添加8? 這可以嗎? 我們的偶然問題可能源於此時發生的中斷,並導致ebx在浮點數加載時沒有8的偏移量嗎?
我希望這是正確的唯一方法是添加操作在fld之前。 很難理解這根本有用......
有沒有辦法關閉這種重排優化?
編輯:ConfigReader看起來像這樣
class ConfigReader
{
public:
static ConfigReader& Instance();
const ConfigurationParams& Parameters() const { return myParameters; }
private:
ConfigReader();
float internalParam1;
float internalParam2;
ConfigurationParams myParameters;
}
struct ConfigurationParams
{
char s1[10];
char s2[50];
int i1;
int i2;
int i3;
int i4;
int i5;
int i6;
int i7;
int i8;
int i9;
int i10;
int i11;
int i12;
int i13;
int i14;
int i15;
int i16;
int i17;
int i18;
int i19;
int i20;
int i21;
int i22;
int i23;
float f1;
float f2;
int i25;
int i26;
int i27;
int i28;
int i29;
int i30;
int i31;
bool b1;
float f3;
float f4;
float f5;
float p1;
float p3;
float p4;
float f9;
float f10;
float f11;
float f12;
float p2;
float f14;
float f15;
float f16;
int i32;
int i33;
int i34;
int i35;
}
實際上,這個fld
對我來說是正確的。 這是看起來它正在采取錯誤價值的fmul
。 生成的代碼正在執行:
float m1 = configParams.p1 * configParams.f14;
鑒於您正在使用優化進行編譯,並且您沒有發布完整的代碼,您確定它不會相對於您的代碼執行無序的操作嗎? 或者,您確定struct
定義是正確的嗎? 看起來你已經匿名化並縮寫了這一點,從而使發布的代碼與你實際看到的不同。
我幾乎不相信編譯器生成的變量地址存在錯誤。 這不是不可能,但非常罕見。 並且必須首先排除這一點,但看起來我們沒有提供所有相關代碼來對此作出判斷。
然而,最有可能發生的是編譯器正在執行以下一項或兩項操作:
(1)的結果是,盡管從數學角度來看優化是正確的,但數學公理在精度有限的計算中停止工作,這就是為什么任意(在編譯器的突發奇想)重新排序導致結果與你可能的不同期待所有或相對於相同代碼的未優化版本中的結果。
事實上,在大多數計算機中,浮點運算的工作原理如下:
(a + b)+ c≠a +(b + c)
(a * b)* c≠a *(b * c)
(a + b)* c≠(a * c)+(b * c)
...等等(參見Knuth的TAOCP或已經提到的Microsoft Visual C ++浮點優化 )。
因此,重新排序浮點運算通常不利於一致性。
根據C標准允許的(2)的結果是,您可以在不同優化的代碼中獲得具有不同精度的中間結果。 預計最終結果也會有所不同。
我要做的是首先嘗試/fp:strict
,if /fp:precise
是否正在使用(這是默認值)。 當然,如果你需要一致性,你不希望/fp:fast
。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.