.NET JIT潛在錯誤?

在Visual Studio中運行發行版並在Visual Studio外部運行發行版時,如下代碼提供了不一樣的輸出。 我正在使用Visual Studio 2008並以.NET 3.5爲目標。 我也嘗試過.NET 3.5 SP1。 oop

當在Visual Studio外部運行時,JIT應該啓動。或者(a)C#中有一些微妙的東西我缺失或者(b)JIT其實是錯誤的。 我懷疑JIT可能出錯,但我已經沒有其餘可能性...... 優化

在Visual Studio中運行時的輸出: this

0 0,
    0 1,
    1 0,
    1 1,

在Visual Studio外部運行發佈時的輸出: spa

0 2,
    0 2,
    1 2,
    1 2,

是什麼緣由? 調試

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Test
{
    struct IntVec
    {
        public int x;
        public int y;
    }

    interface IDoSomething
    {
        void Do(IntVec o);
    }

    class DoSomething : IDoSomething
    {
        public void Do(IntVec o)
        {
            Console.WriteLine(o.x.ToString() + " " + o.y.ToString()+",");
        }
    }

    class Program
    {
        static void Test(IDoSomething oDoesSomething)
        {
            IntVec oVec = new IntVec();
            for (oVec.x = 0; oVec.x < 2; oVec.x++)
            {
                for (oVec.y = 0; oVec.y < 2; oVec.y++)
                {
                    oDoesSomething.Do(oVec);
                }
            }
        }

        static void Main(string[] args)
        {
            Test(new DoSomething());
            Console.ReadLine();
        }
    }
}

#1樓

我將您的代碼複製到新的控制檯應用程序中。 rest

  • 調試構建
    • 使用調試器和無調試器來更正輸出
  • 切換到發佈版本
    • 再次,正確輸出兩次
  • 建立了一個新的x86配置(我正在運行X64 Windows 2008並使用'Any CPU')
  • 調試構建
    • 獲得正確的輸出F5和CTRL + F5
  • 發佈版本
    • 鏈接調試器時輸出正確
    • 沒有調試器 - 輸出錯誤

因此它是x86 JIT錯誤地生成代碼。 已經刪除了關於循環從新排序的原始文本等。此處的一些其餘答案已經確認JIT在x86上不正確地展開循環。 code

要解決此問題,您能夠將IntVec的聲明更改成類,而且它適用於全部類型。 排序

認爲這須要繼續MS Connect .... rem

-1到微軟! string


#2樓

我相信這是一個真正的JIT編譯錯誤。 我會向微軟報告,看看他們說了什麼。 有趣的是,我發現x64 JIT沒有一樣的問題。

這是我對x86 JIT的閱讀。

// save context
00000000  push        ebp  
00000001  mov         ebp,esp 
00000003  push        edi  
00000004  push        esi  
00000005  push        ebx  

// put oDoesSomething pointer in ebx
00000006  mov         ebx,ecx 

// zero out edi, this will store oVec.y
00000008  xor         edi,edi 

// zero out esi, this will store oVec.x
0000000a  xor         esi,esi 

// NOTE: the inner loop is unrolled here.
// set oVec.y to 2
0000000c  mov         edi,2 

// call oDoesSomething.Do(oVec) -- y is always 2!?!
00000011  push        edi  
00000012  push        esi  
00000013  mov         ecx,ebx 
00000015  call        dword ptr ds:[002F0010h] 

// call oDoesSomething.Do(oVec) -- y is always 2?!?!
0000001b  push        edi  
0000001c  push        esi  
0000001d  mov         ecx,ebx 
0000001f  call        dword ptr ds:[002F0010h] 

// increment oVec.x
00000025  inc         esi  

// loop back to 0000000C if oVec.x < 2
00000026  cmp         esi,2 
00000029  jl          0000000C 

// restore context and return
0000002b  pop         ebx  
0000002c  pop         esi  
0000002d  pop         edi  
0000002e  pop         ebp  
0000002f  ret

這看起來像是對我不利的優化......


#3樓

這是一個JIT優化器錯誤。 它展開內部循環但不正確更新oVec.y值:

for (oVec.x = 0; oVec.x < 2; oVec.x++) {
0000000a  xor         esi,esi                         ; oVec.x = 0
        for (oVec.y = 0; oVec.y < 2; oVec.y++) {
0000000c  mov         edi,2                           ; oVec.y = 2, WRONG!
          oDoesSomething.Do(oVec);
00000011  push        edi  
00000012  push        esi  
00000013  mov         ecx,ebx 
00000015  call        dword ptr ds:[00170210h]        ; first unrolled call
0000001b  push        edi                             ; WRONG! does not increment oVec.y
0000001c  push        esi  
0000001d  mov         ecx,ebx 
0000001f  call        dword ptr ds:[00170210h]        ; second unrolled call
      for (oVec.x = 0; oVec.x < 2; oVec.x++) {
00000025  inc         esi  
00000026  cmp         esi,2 
00000029  jl          0000000C

當你讓oVec.y增長到4時,這個bug就會消失,那就是要打開的次數過多。

一個解決方法是:

for (int x = 0; x < 2; x++) {
    for (int y = 0; y < 2; y++) {
      oDoesSomething.Do(new IntVec(x, y));
    }
  }

更新:2012年8月從新檢查,此錯誤已在版本4.0.30319抖動中修復。 但仍然存在於v2.0.50727抖動中。 在這麼久以後,他們彷佛不太可能在舊版本中解決這個問題。

相關文章
相關標籤/搜索