下面是一个简单的测试夹具。它在调试版本中成功,在发布版本中失败(VS2010、.NET4 解决方案、x64):
[TestFixture]
public sealed class Test
{
[Test]
public void TestChecker()
{
var checker = new Checker();
Assert.That(checker.IsDateTime(DateTime.Now), Is.True);
}
}
public class Checker
{
public bool IsDateTime(object o)
{
return o is DateTime;
}
}
代码优化似乎造成了一些破坏;如果我在发布版本上禁用它,它也可以工作。这让我很困惑。下面,我使用 ILDASM 反汇编了构建的 2 个版本:
调试IL:
.method public hidebysig instance bool IsDateTime(object o) cil managed
{
// Code size 15 (0xf)
.maxstack 2
.locals init (bool V_0)
IL_0000: nop
IL_0001: ldarg.1
IL_0002: isinst [mscorlib]System.DateTime
IL_0007: ldnull
IL_0008: cgt.un
IL_000a: stloc.0
IL_000b: br.s IL_000d
IL_000d: ldloc.0
IL_000e: ret
} // end of method Validator::IsValid
释放IL:
.method public hidebysig instance bool IsDateTime(object o) cil managed
{
// Code size 10 (0xa)
.maxstack 8
IL_0000: ldarg.1
IL_0001: isinst [mscorlib]System.DateTime
IL_0006: ldnull
IL_0007: cgt.un
IL_0009: ret
} // end of method Validator::IsValid
似乎商店和负载都被优化掉了。针对早期版本的 .NET 框架使问题消失,但这可能只是侥幸。我发现这种行为有点令人不安,任何人都可以解释为什么编译器会认为进行产生不同可观察行为的优化是安全的吗?
与C#编译器无关,IL是相同的。 您在 .NET 4.0 抖动优化器中发现了一个错误。 您可以在 Visual Studio 中重现它。 工具 + 选项、调试、常规,取消选中“抑制模块加载时的 JIT 优化”选项并运行发布版本以重现故障。
我还没有足够仔细地观察它来识别错误。 它看起来很奇怪,它内联了方法并完全省略了装箱转换的代码。 机器代码与版本 2 抖动生成的代码有很大不同。
干净的解决方法并不那么容易,您可以通过抑制内联来实现。 像这样:
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
public bool IsDateTime(object o) {
return o is DateTime;
}
您可以在 connect.microsoft.com 上报告该错误。 如果您不愿意,请告诉我,我会处理的。
没关系,那已经是已经完成了。 VS2010 SP1 附带的维护版本中未修复此问题。
此错误已修复,我无法再重现它。 我当前的 clrjit.dll 版本是 4.0.30319.237,日期为 2011 年 5 月 17 日。我无法确切地说出哪个更新修复了它。 我在 2011 年 8 月 5 日收到了安全更新,将 clrjit.dll 更新为修订版 235,日期为 4 月 12 日,这将是最早的日期。
就流量控制而言,存储和加载本质上是一个 nop,但可能会以某种方式调整一些 CPU 缓存。 实际流程只是将参数加载到堆栈上,检查它是否是一个实例(返回 null 或实例),将 null 压入堆栈,然后比较(大于),这会导致布尔值留在堆栈上。
现在 JITter 用它做什么完全是另一个故事(并且取决于您使用的平台。JITter 会以性能的名义做各种疯狂的事情(我们的团队最近受到了打击,因为尾部调用优化更改为跨域边界进行优化,这会破坏 GetCallingAssembly())。 JITter 可能会内联 IsDateTime,注意到它不可能不是 DateTime,只是将 true 推入堆栈。
您的发布版本也可能针对稍微不同的框架,因此测试程序集中的日期时间不是测试程序集中的日期时间。我意识到这并不能回答你的代码被破坏的原因。
.method public hidebysig
instance default bool IsDateTime (object o) cil managed
{
// Method begins at RVA 0x2130
// Code size 10 (0xa)
.maxstack 8
IL_0000: ldarg.1
IL_0001: isinst [mscorlib]System.DateTime
IL_0006: ldnull
IL_0007: cgt.un
IL_0009: ret
} // end of method Checker::IsDateTime
没有优化是
完全一样
这是该 IL 的 mono 即时代码的结果:
00000130 <TestData_Checker_IsDateTime_object>:
130: 55 push %ebp
131: 8b ec mov %esp,%ebp
133: 53 push %ebx
134: 56 push %esi
135: 83 ec 10 sub $0x10,%esp
138: e8 00 00 00 00 call 13d <TestData_Checker_IsDateTime_object+0xd>
13d: 5b pop %ebx
13e: 81 c3 03 00 00 00 add $0x3,%ebx
144: 8b 45 0c mov 0xc(%ebp),%eax
147: 89 45 f4 mov %eax,-0xc(%ebp)
14a: 8b 75 0c mov 0xc(%ebp),%esi
14d: 83 7d 0c 00 cmpl $0x0,0xc(%ebp)
151: 74 1a je 16d <TestData_Checker_IsDateTime_object+0x3d>
153: 8b 45 f4 mov -0xc(%ebp),%eax
156: 8b 00 mov (%eax),%eax
158: 8b 00 mov (%eax),%eax
15a: 8b 40 08 mov 0x8(%eax),%eax
15d: 8b 48 08 mov 0x8(%eax),%ecx
160: 8b 93 10 00 00 00 mov 0x10(%ebx),%edx
166: 33 c0 xor %eax,%eax
168: 3b ca cmp %edx,%ecx
16a: 0f 45 f0 cmovne %eax,%esi
16d: 85 f6 test %esi,%esi
16f: 0f 97 c0 seta %al
172: 0f b6 c0 movzbl %al,%eax
175: 8d 65 f8 lea -0x8(%ebp),%esp
178: 5e pop %esi
179: 5b pop %ebx
17a: c9 leave
17b: c3 ret
17c: 8d 74 26 00 lea 0x0(%esi,%eiz,1),%esi