.NET 3.5 JIT在运行应用程序时无法工作

下面的代码给出了在Visual Studio内部运行版本和在Visual Studio外部运行版本时不同的输出。我使用的是Visual Studio 2008,目标是。net 3.5。我也尝试过。net 3.5 SP1。

当在Visual Studio之外运行时,JIT应该起作用。要么(a)我忽略了c#中一些微妙的东西,要么(b) JIT实际上是错误的。我怀疑JIT会出问题,但我已经没有其他可能性了……

在Visual Studio中运行时输出:

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

在Visual Studio外部运行release时的输出:

    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();
}
}
}
8244 次浏览

我把你的代码复制到一个新的控制台应用程序中。

    <李>调试构建
    • 带有调试器和没有调试器的正确输出
    • 李< / ul > < / >
    • 切换到发布版本
      • 同样,两次输出都要正确
      • 李< / ul > < / >
      • 创建了一个新的x86配置(我正在运行X64 Windows 2008,并使用“任何CPU”)
      • <李>调试构建
        • 得到了正确的输出F5和CTRL+F5
        • 李< / ul > < / > <李>发布构建
          • 带有调试器的正确输出
          • 没有调试器- 得到不正确的输出
          • 李< / ul > < / >

          所以是x86 JIT错误地生成了代码。已经删除了我的原始文本关于循环的重新排序等。这里的其他一些答案已经证实了JIT在x86上不正确地展开循环。

          要解决这个问题,你可以将IntVec声明为一个类,它可以在所有类型中工作。

          我认为这需要上MS Connect....

          -1到微软!

我相信这是一个真正的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

对我来说,这看起来像是一个坏的优化……

这是一个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,展开的调用太多了。

一个解决办法是:

  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抖动中。在这么长时间之后,他们似乎不太可能在旧版本中修复这个问题。