.NET 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(oxToString() + " " + oyToString()+","); } } 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值:
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月重新检查,此bug在版本4.0.30319抖动修复。 但在v2.0.50727抖动中仍然存在。 经过这么长时间,似乎不太可能在旧版本中解决这个问题。
我相信这是一个真正的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
这看起来像一个优化对我来说不好…
我将您的代码复制到新的控制台应用程序。
- debugging版本
- 使用debugging器和无debugging器纠正输出
- 切换到发布版本
- 再次,正确的输出两次
- 创build了一个新的x86configuration(我正在运行X64 Windows 2008并使用“任何CPU”)
- debugging版本
- 得到正确的输出F5和CTRL + F5
- 发布构build
- 使用附加的debugging器纠正输出
- 没有debugging器 – 得到不正确的输出
所以这是x86 JIT错误地生成代码。 已经删除了我有关循环重新sorting的原始文本等。这里的一些其他答案已经证实,在x86上,JIT是错误地展开循环。
为了解决这个问题,你可以把IntVec的声明改成一个类,它可以适用于所有的types。
认为这需要继续MS Connect ….
-1给微软!