41

下面是一个简单的测试夹具。它在 Debug 构建中成功,在 Release 构建中失败(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;
    }
}

似乎代码优化造成了一些破坏;如果我在 Release 版本中禁用它,它也可以工作。这让我很费解。下面,我使用 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 框架解决了问题,但这可能只是侥幸。我发现这种行为有点令人不安,任何人都可以解释为什么编译器会认为进行产生不同可观察行为的优化是安全的吗?

提前致谢。

4

4 回答 4

21

Jacob Stanley在这个 SO question中已经出现了这个错误。Jacob 已经报告了这个 bug,微软已经确认它确实是 CLR JIT 中的一个 bug。微软有这样的说法:

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

再次感谢您报告该问题。

您应该能够通过将以下属性添加到来解决该错误TestChecker()

[MethodImpl(MethodImplOptions.NoInlining)]
于 2011-04-04T20:26:33.287 回答
16

它与 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 日,这将是最早的。

于 2011-04-04T20:20:58.747 回答
5

就流控制而言,存储和加载本质上是一个 nop,但可能会以某种方式处理一些 CPU 缓存。实际流程只是将参数加载到堆栈上,检查它是否是一个实例(返回 null 或实例),将 null 压入堆栈,并比较(大于)导致堆栈上留下一个布尔值。

现在 JITter 用它做什么完全是另一个故事(并且取决于您使用的平台。JITter 会以性能的名义做各种疯狂的事情(我们的团队最近受到打击,因为尾调用优化改为优化跨越破坏 GetCallingAssembly()) 的域边界。JITter 可能正在内联 IsDateTime,注意到它不可能不是 DateTime 并且只是将 true 推入堆栈。

您的发布版本也可能针对稍微不同的框架,因此测试程序集中的 DateTime 不是测试程序集中的 DateTime。

我意识到这并不能回答为什么你的代码会被破坏。

于 2011-04-04T20:21:27.807 回答
3

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

  • Mono JIT 编译器版本 2.6.7 (Debian 2.6.7-3ubuntu1)
  • Mono JIT 编译器版本 2.8.2 (sehe/d1c74ad Fri Feb 18 21: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 的 jit 代码的结果:

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
于 2011-04-04T20:29:47.337 回答