C#“是”运算符在.NET 4上的释放模式优化下受到影响吗?

下面是一个简单的testing夹具。 它在debugging版本中成功并在发布版本(VS2010,.NET4解决scheme,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; } } 

看起来代码优化造成了一些破坏。 如果我在Release版本上禁用它,它也可以工作。 这令我感到困惑。 下面我使用ILDASM来反汇编这个版本的两个版本:

debuggingIL:

 .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框架使问题消失,但这可能只是一个侥幸。 我发现这种行为有点不安,任何人都可以解释为什么编译器会认为它是安全的做一个优化,产生不同的可观察到的行为?

提前致谢。

雅各布·斯坦利在这个问题上已经出现了这个错误。 雅各布已经报告了这个错误 ,微软已经确认这确实是CLR JIT中的一个错误。 微软有这样的说法:

该错误将在未来版本的运行时中修复。 恐怕现在判断是否会在服务包或下一个主要版本中还为时过早。

再次感谢您报告此问题。

您应该能够通过向TestChecker()添加以下属性来解决该问题:

 [MethodImpl(MethodImplOptions.NoInlining)] 

它与C#编译器无关,IL是相同的。 您在.NET 4.0抖动优化器中发现了一个错误。 您可以在Visual Studio中重新制作它。 工具+选项,debugging,常规,取消“禁止模块加载的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中包含的维护版本中没有修复。


这个bug已经修复了,我再也不能再重复它了。 我当前版本的clrjit.dll是4.0.30319.237date为2011年5月17日。我无法确切地知道什么更新修复它。 2011年8月5日我收到了一个安全更新,更新clrjit.dll到版本235,date为4月12日,这将是最早的。

就stream量控制而言,存储和加载本质上是一个nop,但是可能以某种方式按下某些CPUcaching。 实际stream只是加载堆栈上的参数,检查它是否是一个实例(返回null或实例),将null推入堆栈,并比较(大于),导致堆栈中留下布尔值。

现在JITter完成的是另外一个故事(取决于你使用的是什么平台),JITter会以性能的名义做各种疯狂的事情(我们的团队最近受到打击,因为tail-call优化改为优化跨域破坏了GetCallingAssembly()),可能JITter内嵌IsDateTime,注意到没有办法不能不是一个DateTime,只是将true推到堆栈上。

也有可能你的发布版本的目标是稍有不同的框架,所以testing程序集中的DateTime不是testing程序集中的DateTime。

我意识到,不会回答为什么你的代码打破。

作为参考,我用单声道检查

  • 单声道JIT编译器版本2.6.7(Debian 2.6.7-3ubuntu1)
  • 单声道JIT编译器版本2.8.2(sehe / d1c74ad Fri Feb 18 18:46:52 CET 2011)

两者都没有提出任何问题。 这里是在2.8.2中优化的IL

 .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的jitted代码的结果:

 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