在调试模式下优化 C# 代码,优化代码未选中

Dar*_*xis 6 c# debugging compiler-optimization visual-studio

Debug尽管Optimize code禁用了某些 C# 代码,但我还是在模式下进行了优化。

代码

namespace Test
{
    internal class Program
    {
        public class Test
        {
            public void TestMethod(decimal x, out decimal result)
            {
                result = x / 100m;
                //Console.WriteLine($"{x} {result}");
                result++;
                //Console.WriteLine($"{x} {result}");
            }
        }

        private static void Main()
        {
            var x = new Test();
            x.TestMethod(12300m, out _);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

构建配置

调试时,我可以看到以下内容:

调试步骤 1 调试步骤 2 调试步骤 3

正如您在第二步中看到的,xresult变量都被修改了,但唯一预计要修改的是result.

现在对这个问题进行一些分析。我的猜测是它可能是由两件事引起的:

  1. Visual Studio 调试器中的错误
  2. 优化代码的编译器

如果它是 Visual Studio 调试器中的错误,那么为什么取消注释Console.WriteLine代码可以解决此问题

如果优化代码的是编译器,那么为什么它在 dotPeek 反编译源中不可见?CLR 或其他东西是否有可能在运行时优化代码?dotPeek反编译的汇编源代码如下:

// Decompiled with JetBrains decompiler
// Type: Test.Program
// Assembly: Test, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
// MVID: 010328D8-C177-4463-81CD-43CDDFF608A4
// Assembly location: XXX

using System;

namespace Test
{
  internal class Program
  {
    private static void Main()
    {
      new Program.Test().TestMethod(new Decimal(12300), out Decimal _);
    }

    public class Test
    {
      public void TestMethod(Decimal x, out Decimal result)
      {
        result = x / new Decimal(100);
        ++result;
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

我已经尝试过但还没有解决这个问题:

  • 清洁解决方案 + 构建解决方案
  • 重建解决方案
  • 清理项目 + 构建项目
  • 重建项目
  • 检查Optimize code标志,构建解决方案,取消选中它,再次构建解决方案(在/sf/answers/2190804031/ 中建议)
  • 关闭 Visual Studio 并删除隐藏.vs目录

编辑1:

这是 MSIL 代码

.method public hidebysig instance void  TestMethod(valuetype [mscorlib]System.Decimal x,
                                                   [out] valuetype [mscorlib]System.Decimal& result) cil managed
{
  // Code size       38 (0x26)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ldarg.2
  IL_0002:  ldarg.1
  IL_0003:  ldc.i4.s   100
  IL_0005:  newobj     instance void [mscorlib]System.Decimal::.ctor(int32)
  IL_000a:  call       valuetype [mscorlib]System.Decimal [mscorlib]System.Decimal::op_Division(valuetype [mscorlib]System.Decimal,
                                                                                                valuetype [mscorlib]System.Decimal)
  IL_000f:  stobj      [mscorlib]System.Decimal
  IL_0014:  ldarg.2
  IL_0015:  ldarg.2
  IL_0016:  ldobj      [mscorlib]System.Decimal
  IL_001b:  call       valuetype [mscorlib]System.Decimal [mscorlib]System.Decimal::op_Increment(valuetype [mscorlib]System.Decimal)
  IL_0020:  stobj      [mscorlib]System.Decimal
  IL_0025:  ret
} // end of method Test::TestMethod
Run Code Online (Sandbox Code Playgroud)

编辑2:

@HansPassant 感谢您的评论,您提到此问题特定于 x64。好吧,我已经检查过Prefer 32-bit,问题已经解决了,所以它一定是x64问题!我在内存视图打开的情况下再次调试了该应用程序。我们可以看到内存中的两个变量都发生了变化,因此这可能是您提到的 x64 代码生成中的一个错误。我现在要调试 x64 代码...

内存 1

内存2

编辑 3(最终?):

我已经调试了 x64 代码,结果发现运行时生成了优化的 x64 代码......

下面是两种情况下生成的 x64 汇编代码,一种System.Console.WriteLine是在被调用时(代码未优化),一种System.Console.WriteLine是在被注释时(代码不应优化但已优化):

With System.Console.WriteLine calls:
    result = x / 100m;
00007FFD078809F7  lea         rcx,[rbp+0D8h]  
00007FFD078809FE  vxorps      xmm0,xmm0,xmm0  
00007FFD07880A03  vmovdqu     xmmword ptr [rcx],xmm0  
00007FFD07880A08  lea         rcx,[rbp+0D8h]  
00007FFD07880A0F  mov         edx,64h  
00007FFD07880A14  call        00007FFD64B42280 // System.Decimal..ctor(Int32) // new Decimal(100)
00007FFD07880A19  mov         rcx,qword ptr [rbp+120h] // rcx = &result
00007FFD07880A20  mov         qword ptr [rbp+0D0h],rcx // [rbp+0D0h] = rcx -> &result
00007FFD07880A27  lea         rcx,[rbp+0C0h] // ?
00007FFD07880A2E  mov         qword ptr [rbp+70h],rcx // ?
00007FFD07880A32  mov         rcx,qword ptr [rbp+118h] // rcx = &x
00007FFD07880A39  vmovdqu     xmm0,xmmword ptr [rcx] // xmm0 = rcx -> x  <--- Here we copy 'x' value to xmm0
00007FFD07880A3E  vmovdqu     xmmword ptr [rbp+88h],xmm0 // [rbp+88h] = xmm0 -> x <--- Here we copy xmm0 ('x' value) to [rbp+88h]
00007FFD07880A47  vmovdqu     xmm0,xmmword ptr [rbp+0D8h] // xmm0 = copy of 100m
00007FFD07880A50  vmovdqu     xmmword ptr [rbp+78h],xmm0 // [rbp+78h] = xmm0 -> copy of 100m
00007FFD07880A56  mov         rcx,qword ptr [rbp+70h] // ?
00007FFD07880A5A  lea         rdx,[rbp+88h] // rdx = [rbp+88h] -> copy of 'x' as first op_Division arg
00007FFD07880A61  lea         r8,[rbp+78h] // R8 = [rbp+78h] -> 100m as second op_Division arg
00007FFD07880A65  call        00007FFD64B440A0 // Call System.Decimal.op_Division(System.Decimal, System.Decimal)
00007FFD07880A6A  mov         rdx,qword ptr [rbp+0D0h]  
00007FFD07880A71  vmovdqu     xmm0,xmmword ptr [rbp+0C0h]  
00007FFD07880A7A  vmovdqu     xmmword ptr [rdx],xmm0  

With System.Console.WriteLine calls commented:
    result = x / 100m;
00007FFD078909F7  lea         rcx,[rbp+88h]  
00007FFD078909FE  vxorps      xmm0,xmm0,xmm0  
00007FFD07890A03  vmovdqu     xmmword ptr [rcx],xmm0  
00007FFD07890A08  lea         rcx,[rbp+88h]  
00007FFD07890A0F  mov         edx,64h  
00007FFD07890A14  call        00007FFD64B42280 // System.Decimal..ctor(Int32) // new Decimal(100)
00007FFD07890A19  mov         rcx,qword ptr [rbp+0D0h] // rcx = &result
00007FFD07890A20  mov         qword ptr [rbp+80h],rcx // [rbp+80h] = rcx -> &result
00007FFD07890A27  lea         rcx,[rbp+70h] // ?
00007FFD07890A2B  mov         qword ptr [rbp+40h],rcx // ?
00007FFD07890A2F  mov         rcx,qword ptr [rbp+0C8h] // rcx = &x
00007FFD07890A36  mov         qword ptr [rbp+38h],rcx // [rbp+38h] = rcx -> &x
00007FFD07890A3A  vmovdqu     xmm0,xmmword ptr [rbp+88h] // xmm0 = copy of 100m
00007FFD07890A43  vmovdqu     xmmword ptr [rbp+48h],xmm0 // [rbp+48h] = xmm0 -> copy of 100m
00007FFD07890A49  mov         rcx,qword ptr [rbp+40h] // ?
00007FFD07890A4D  mov         rdx,qword ptr [rbp+38h] // rdx = [rbp+38h] = &x -> 'x' instance address as first op_Division arg
00007FFD07890A51  lea         r8,[rbp+48h] // r8 = [rbp+48h] -> copy of 100m as second op_Division arg
00007FFD07890A55  call        00007FFD64B440A0 // Call System.Decimal.op_Division(System.Decimal, System.Decimal)
00007FFD07890A5A  mov         rcx,qword ptr [rbp+80h]  
00007FFD07890A61  vmovdqu     xmm0,xmmword ptr [rbp+70h]  
00007FFD07890A67  vmovdqu     xmmword ptr [rcx],xmm0  
Run Code Online (Sandbox Code Playgroud)

如您所见,它们很相似,但第一个将x值复制到新的内存位置:

00007FFD07880A39  vmovdqu     xmm0,xmmword ptr [rcx] // xmm0 = rcx -> x  <--- Here we copy 'x' value to xmm0
00007FFD07880A3E  vmovdqu     xmmword ptr [rbp+88h],xmm0 // [rbp+88h] = xmm0 -> x <--- Here we copy xmm0 ('x' value) to [rbp+88h]
Run Code Online (Sandbox Code Playgroud)

然后将其传递给System.Decimal.op_Division(System.Decimal, System.Decimal)函数。但是第二个不复制它,直接传递它的引用;它缺少上面的两个说明。

我已经提交了一个错误,正如@HansPassant 所建议的那样,并带有指向此 SO 问题的链接。链接:https : //developercommunity.visualstudio.com/content/problem/992021/c-code-being-optimized-in-debug-mode-with-optimize.html