У меня проблема с некоторым кодом 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](https://i.stack.imgur.com/FSuxd.png)
При отладке я вижу следующее:
![Debug step 3](https://i.stack.imgur.com/R3NGH.png)
Как вы можете видеть на втором шаге, переменные x
и result
были изменены, но единственной ожидаемой для изменения была result
.
Теперь немного проанализируем эту проблему. Я предполагаю, что это может быть вызвано двумя причинами:
- Ошибка в отладчике Visual Studio
- Компилятор, который оптимизирует код
Если это ошибка в отладчике 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](https://i.stack.imgur.com/jVnC1.png)
![Memory 2](https://i.stack.imgur.com/WqgJJ.png)
Редактировать 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