.NET 3.5 JIT 在运行应用程序时不起作用
Posted
技术标签:
【中文标题】.NET 3.5 JIT 在运行应用程序时不起作用【英文标题】:.NET 3.5 JIT not working when running the application 【发布时间】:2011-01-04 15:27:23 【问题描述】:以下代码在 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 中的错误,微软干得好。 我们都在焦急地等待微软的回复..... 【参考方案1】:这是一个 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,从调试器的反汇编窗口复制/粘贴并手动添加 cmets。【参考方案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】:我将您的代码复制到了一个新的控制台应用程序中。
调试构建 使用调试器和不使用调试器的正确输出 切换到发布版本 再次,两次正确输出 创建了一个新的 x86 配置(我正在运行 X64 Windows 2008 并且使用的是“任何 CPU”) 调试构建 F5 和 CTRL+F5 都得到了正确的输出 发布版本 附加调试器的正确输出 没有调试器 - 得到不正确的输出所以是 x86 JIT 错误地生成了代码。已删除我关于循环重新排序等的原始文本。此处的其他一些答案已确认 JIT 在 x86 上错误地展开循环。
要解决此问题,您可以将 IntVec 的声明更改为一个类,它适用于所有风格。
认为这需要在 MS Connect 上继续......
-1 微软!
【讨论】:
有趣的想法,但如果是这种情况,这肯定不是“优化”而是编译器中的一个非常重要的错误?现在应该已经找到了吧? 我同意你的看法。像这样重新排序循环可能会导致无法解决的问题。实际上这似乎更不可能,因为 for 循环永远无法达到 2。 看起来像这些讨厌的海森虫之一:P 如果 OP(或使用他的应用程序的任何人)拥有 32 位 x86 机器,则任何 CPU 都将无法工作。问题是启用优化的 x86 JIT 会生成错误代码。以上是关于.NET 3.5 JIT 在运行应用程序时不起作用的主要内容,如果未能解决你的问题,请参考以下文章
ASP.NET Core MVC 身份功能在发布到服务器时不起作用
如何使用 msbuild 引导 .NET 3.5 SP1?按照找到的说明不起作用