ChatGPT解决这个技术问题 Extra ChatGPT

.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 之外运行发布时的输出:

    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();
        }
    }
}
是的 - 那怎么样:在 .Net JIT 这样重要的东西中发现一个严重的错误 - 恭喜!
这似乎在我 12 月 9 日在 x86 上构建的 4.0 框架中重现。我会把它传递给 jitter 团队。谢谢!
这是为数不多的真正值得获得金牌的问题之一。
我们都对这个问题感兴趣的事实表明,我们不希望 .NET JIT 中出现错误,微软做得很好。
我们都在焦急地等待微软的回复......

H
Hans Passant

这是一个 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 抖动中。过了这么久,他们似乎不太可能在旧版本中解决这个问题。


+1,绝对是一个错误 - 我可能已经确定了错误的条件(不是说 nobugz 是因为我发现它!),但是这个(和你的,尼克,所以也为你 +1)表明 JIT是罪魁祸首。有趣的是,当 IntVec 被声明为一个类时,优化要么被删除,要么不同。即使您在循环之前先将结构字段显式初始化为 0,也会看到相同的行为。讨厌!
@Hans Passant 你用什么工具来输出汇编代码?
@Joan - 只是 Visual Studio,从调试器的反汇编窗口复制/粘贴并手动添加注释。
N
Nick Guerrera

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

这看起来像优化对我来说很糟糕......


A
Andras Zoltan

我将您的代码复制到了一个新的控制台应用程序中。

Debug Build 使用调试器和无调试器的正确输出

使用调试器和没有调试器的正确输出

切换到 Release Build Again,两次都正确输出

同样,两次正确输出

创建了一个新的 x86 配置(我正在运行 X64 Windows 2008 并且正在使用“任何 CPU”)

Debug Build 得到正确的输出 F5 和 CTRL+F5

F5 和 CTRL+F5 都得到了正确的输出

Release Build Correct output with Debugger 没有调试器 - 得到不正确的输出

附加调试器的正确输出

没有调试器 - 得到不正确的输出

所以是 x86 JIT 错误地生成了代码。已删除有关循环重新排序等的原始文本。此处的其他一些答案已确认 JIT 在 x86 上错误地展开循环。

要解决此问题,您可以将 IntVec 的声明更改为一个类,它适用于所有风格。

认为这需要继续 MS Connect....

-1 微软!


有趣的想法,但如果是这种情况,这肯定不是“优化”而是编译器中的一个非常重要的错误?现在应该已经找到了吧?
我同意你的看法。像这样重新排序循环可能会导致无法解决的问题。实际上,这似乎更不可能,因为 for 循环永远无法达到 2。
看起来像这些讨厌的海森虫之一:P
如果 OP(或任何使用他的应用程序的人)拥有 32 位 x86 机器,则任何 CPU 都将无法工作。问题是启用优化的 x86 JIT 会生成错误代码。