Dot-Net

CLR JIT 優化違反因果關係?

  • May 10, 2012

我正在為一位同事寫一個有啟發性的範例,向他展示為什麼測試浮點數是否相等通常是一個壞主意。我使用的範例是添加 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 上提出您的意見

引用自:https://stackoverflow.com/questions/2225503