.NET JIT potential error?
以下代码在Visual Studio内部运行版本,在Visual Studio外部运行版本时提供不同的输出。我使用的是Visual Studio 2008,目标是.NET 3.5。我还尝试了.NET 3.5 SP1。
在Visual Studio外部运行时,JIT应该启动。要么(a)我找不到与c有关的微妙之处,要么(b)JIT实际上出错了。我怀疑JIT会出问题,但我正在用尽其他可能性…
在Visual Studio中运行时的输出:
1 2 3 4 | 0 0, 0 1, 1 0, 1 1, |
在Visual Studio外部运行版本时输出:
1 2 3 4 | 0 2, 0 2, 1 2, 1 2, |
原因是什么?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | 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(); } } } |
这是一个JIT优化器错误。它正在展开内部循环,但未正确更新ovec.y值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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就消失了,这是太多的调用无法展开。
解决方法之一是:
1 2 3 4 5 | 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抖动中被修复。但仍然存在于2.0.50727抖动中。他们似乎不太可能在这么长时间后在旧版本中修复这个问题。
我相信这是一个真正的JIT编译错误。我会向微软报告,看看他们怎么说。有趣的是,我发现X64 JIT没有同样的问题。
这是我对x86 JIT的理解。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | // 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 |
这看起来优化对我不好…
我把你的代码复制到一个新的控制台应用程序中。
- 调试版本
- 使用调试器和不使用调试器更正输出
- 切换到发布版本
- 再次纠正两次输出
- 创建了一个新的x86配置(我正在运行x64 Windows 2008,并且正在使用'any cpu')
- 调试版本
- 正确输出了f5和ctrl+f5
- 释放构建
- 使用附加的调试程序更正输出
- 没有调试程序-得到了不正确的输出
所以是x86 JIT错误地生成了代码。删除了我关于循环重新排序等的原始文本。这里的一些其他答案已确认,在x86上,JIT正在错误地展开循环。
要解决这个问题,您可以将intvec的声明更改为类,它可以在所有风格中工作。
认为这需要在MS Connect上进行….
-1到Microsoft!