Dot-Net
CLR JIT 優化違反因果關係?
我正在為一位同事寫一個有啟發性的範例,向他展示為什麼測試浮點數是否相等通常是一個壞主意。我使用的範例是添加 0.1 十次,並與 1.0(我在介紹性數字課程中展示的那個)進行比較。我驚訝地發現這兩個結果是相等的(程式碼+輸出)。
float @float = 0.0f; for(int @int = 0; @int < 10; @int += 1) { @float += 0.1f; } Console.WriteLine(@float == 1.0f);一些調查表明,這個結果不能被依賴(很像浮動平等)。我發現最令人驚訝的是,在其他程式碼之後添加程式碼可能會改變計算結果(程式碼 + 輸出)。請注意,此範例具有完全相同的程式碼和 IL,但多了一行 C#。
float @float = 0.0f; for(int @int = 0; @int < 10; @int += 1) { @float += 0.1f; } Console.WriteLine(@float == 1.0f); Console.WriteLine(@float.ToString("G9"));我知道我不應該在浮點數上使用相等,因此不應該太在意這一點,但我發現這非常令人驚訝,就像我向所有人展示過的一樣。在執行計算後做一些事情會改變前面計算的值?我不認為這是人們通常想到的計算模型。
我並沒有完全被難住,假設在“相等”情況下發生某種優化會改變計算結果(在調試模式下建構可以防止“相等”情況),這似乎是安全的。顯然,當 CLR 發現稍後需要對浮點數進行裝箱時,優化就被放棄了。
我搜尋了一下,但找不到這種行為的原因。任何人都可以提示我嗎?
這是 JIT 優化器工作方式的副作用。如果要生成的程式碼更少,它會做更多的工作。原始程式碼段中的循環被編譯為:
@float += 0.1f; 0000000f fld dword ptr ds:[0025156Ch] ; push(intermediate), st0 = 0.1 00000015 faddp st(1),st ; st0 = st0 + st1 for (int @int = 0; @int < 10; @int += 1) { 00000017 inc eax 00000018 cmp eax,0Ah 0000001b jl 0000000F當您添加額外的 Console.WriteLine() 語句時,它會將其編譯為:
@float += 0.1f; 00000011 fld dword ptr ds:[00961594h] ; st0 = 0.1 00000017 fadd dword ptr [ebp-8] ; st0 = st0 + @float 0000001a fstp dword ptr [ebp-8] ; @float = st0 for (int @int = 0; @int < 10; @int += 1) { 0000001d inc eax 0000001e cmp eax,0Ah 00000021 jl 00000011注意地址 15 與地址 17+1a 的區別,第一個循環將中間結果保存在 FPU 中。第二個循環將其儲存回@float 局部變數。雖然它保留在 FPU 內,但結果是以全精度計算的。但是,將其儲存回來會將中間結果截斷回浮點數,從而在此過程中損失大量精度。
雖然不愉快,但我不認為這是一個錯誤。x64 JIT 編譯器的行為有所不同。您可以在 connect.microsoft.com 上提出您的意見