C# код, оптимизируемый в режиме отладки, с опцией оптимизации кода - PullRequest
6 голосов
/ 15 апреля 2020

У меня проблема с некоторым кодом C#, который оптимизируется в режиме Debug, несмотря на то, что Optimize code отключен.

Код

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 _);
        }
    }
}

Build config

При отладке я вижу следующее:

Debug step 1 Debug step 2 Debug step 3

Как вы можете видеть на втором шаге, переменные x и result были изменены, но единственной ожидаемой для изменения была 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;
      }
    }
  }
}

То, что я уже пробовал и не устранил эту проблему:

  • Clean Solution + Build Решение
  • Перестройка решения
  • Очистить проект + построить проект
  • Восстановить проект
  • Установите флажок Optimize code, построить решение, снимите флажок, снова выполните сборку решения (предлагается в { ссылка })
  • Закройте 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

Редактировать 2:

@ HansPassant спасибо за ваш комментарий, вы упомянули, что эта проблема указана от c до x64. Ну, Я проверил Prefer 32-bit, и проблема исчезла , поэтому это должно быть проблема с x64 ! Я снова отладил приложение с открытыми представлениями памяти. Мы можем видеть, как обе переменные изменяются в памяти, так что это может быть ошибкой при генерации кода x64, как вы упоминали. Я собираюсь отладить код x64 сейчас ...

Memory 1

Memory 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  

Как вы можете видеть, они похожи, но первое один копирует значение 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]

, а затем передает его функции 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

...