C# 为什么发行版中的代码行为不同&;调试模式?

C# 为什么发行版中的代码行为不同&;调试模式?,c#,.net,.net-core,coreclr,C#,.net,.net Core,Coreclr,考虑以下代码: private static void Main(string[] args) { var ar = new double[] { 100 }; FillTo(ref ar, 5); Console.WriteLine(string.Join(",", ar.Select(a => a.ToString()).ToArray())); } public static void FillTo(ref double[]

考虑以下代码:

private static void Main(string[] args)
{
    var ar = new double[]
    {
        100
    };

    FillTo(ref ar, 5);
    Console.WriteLine(string.Join(",", ar.Select(a => a.ToString()).ToArray()));
}

public static void FillTo(ref double[] dd, int N)
{
    if (dd.Length >= N)
        return;

    double[] Old = dd;
    double d = double.NaN;
    if (Old.Length > 0)
        d = Old[0];

    dd = new double[N];

    for (int i = 0; i < Old.Length; i++)
    {
        dd[N - Old.Length + i] = Old[i];
    }
    for (int i = 0; i < N - Old.Length; i++)
        dd[i] = d;
}
private static void Main(字符串[]args)
{
var ar=新的双精度[]
{
100
};
FillTo(参考文献ar,5);
Console.WriteLine(string.Join(“,”,ar.Select(a=>a.ToString()).ToArray());
}
公共静态无效填充(参考双[]dd,int N)
{
如果(dd.Length>=N)
返回;
双[]旧=dd;
双d=双NaN;
如果(旧长度>0)
d=旧的[0];
dd=新的双精度[N];
for(int i=0;i
调试模式下的结果为:100100。 但在释放模式下是:100100,0

发生了什么事


它是使用.NET framework 4.7.1和.NET Core 2.0.0进行测试的。

这似乎是一个JIT错误;我测试过:

// ... existing code unchanged
for (int i = 0; i < N - Old.Length; i++)
{
    // Console.WriteLine(i); // <== comment/uncomment this line
    dd[i] = d;
}
vs

这看起来完全正确(唯一的区别是额外的
ldloc.3
调用void[System.Console]System.Console::WriteLine(int32)
,以及一个不同但等效的
br.s
目标)

我想这需要一个JIT修复

环境:

  • 环境。版本
    :4.0.30319.42000
  • netcoreapp2.0
  • VS:15.5.0预览版5.0
  • dotnet--版本
    :2.1.1

这确实是一个装配错误。x64、.NET4.7.1,发布版本

拆卸:

            for(int i = 0; i < N - Old.Length; i++)
00007FF942690ADD  xor         eax,eax  
            for(int i = 0; i < N - Old.Length; i++)
00007FF942690ADF  mov         ebx,esi  
00007FF942690AE1  sub         ebx,ebp  
00007FF942690AE3  test        ebx,ebx  
00007FF942690AE5  jle         00007FF942690AFF  
                dd[i] = d;
00007FF942690AE7  mov         rdx,qword ptr [rdi]  
00007FF942690AEA  cmp         eax,dword ptr [rdx+8]  
00007FF942690AED  jae         00007FF942690B11  
00007FF942690AEF  movsxd      rcx,eax  
00007FF942690AF2  vmovsd      qword ptr [rdx+rcx*8+10h],xmm6  
            for(int i = 0; i < N - Old.Length; i++)
00007FF942690AF9  inc         eax  
00007FF942690AFB  cmp         ebx,eax  
00007FF942690AFD  jg          00007FF942690AE7  
00007FF942690AFF  vmovaps     xmm6,xmmword ptr [rsp+20h]  
00007FF942690B06  add         rsp,30h  
00007FF942690B0A  pop         rbx  
00007FF942690B0B  pop         rbp  
00007FF942690B0C  pop         rsi  
00007FF942690B0D  pop         rdi  
00007FF942690B0E  pop         r14  
00007FF942690B10  ret  
for(int i=0;i
发行地址为00007FF942690AFD,jg 00007FF942690AE7。如果ebx(包含4,循环结束值)大于eax(值i),则返回。当然,当它为4时会失败,因此它不会写入数组中的最后一个元素


它失败了,因为它包含了i的寄存器值(eax,在0x00007FF942690AF9),然后用4检查它,但它仍然必须写入该值。要精确定位问题的确切位置有点困难,因为它看起来可能是(N-Old.Length)优化的结果,因为调试版本包含该代码,但发布版本预先计算了该代码。所以这是由jit人员来解决的;)

您使用哪个版本的Visual Studio(或编译器)?复制;添加
控制台写入线(i)进入最后一个循环(
dd[i]=d;
)“修复”它,这表明编译器错误或JIT错误;查看IL…@Styxxy,在vs2015、2017上测试,并针对每个.net framework>=4.5的版本,肯定是一个bug。如果删除
if(dd.Length>=N)返回,它也会消失
,这可能是一个更简单的复制。毫不奇怪,一旦进行了一一比较,.Net Framework和.Net Core的x64 codegen具有相似的性能,因为(默认情况下)它本质上是相同的jit生成代码。比较.Net Framework x86 codegen与.Net Core的x86 codegen(自2.0以来一直使用RyuJit)的性能会很有趣。在某些情况下,旧的jit(又名Jit32)知道一些RyuJit不知道的技巧。如果你发现任何这样的情况,请确保在CoreCLR repo上为他们打开问题。然后在哪里报告错误?我在.NET full 4.7.1上也看到了,所以如果这不是RyuJIT错误,我就吃我的帽子。我无法复制,已安装.NET 4.7.1,现在可以复制。@MarcGravell.NET framework 4.7.1和.NET Core 2.0。0@AshkanNourzadeh老实说,我可能会将其记录下来,强调人们认为这是一个RyuJIT错误。最近有一天,我需要抽出一些时间来学习汇编/CPU操作码。也许我天真地一直在想“嗯,我可以读写IL-我应该可以摸索一下”-但我从来没有想过:)x64/x86并不是从tho开始的最好的汇编语言;)它有这么多的操作码,我曾经读到没有一个活着的人知道所有的操作码。不确定这是不是真的,但一开始读起来并不容易。尽管它确实使用了一些简单的约定,比如[],源部分之前的目的地以及这些寄存器的全部含义(al是rax的8位部分,eax是rax的32位部分等等)。你可以在vs tho中一步一步地完成它,它会教你一些基本的东西。我相信您会很快找到它,因为您已经知道IL操作码;)
// ...
L_0040: ldc.i4.0 
L_0041: stloc.3 
L_0042: br.s L_0053
L_0044: ldloc.3 
L_0045: call void [System.Console]System.Console::WriteLine(int32)
L_004a: ldarg.0 
L_004b: ldind.ref 
L_004c: ldloc.3 
L_004d: ldloc.1 
L_004e: stelem.r8 
L_004f: ldloc.3 
L_0050: ldc.i4.1 
L_0051: add 
L_0052: stloc.3 
L_0053: ldloc.3 
L_0054: ldarg.1 
L_0055: ldloc.0 
L_0056: ldlen 
L_0057: conv.i4 
L_0058: sub 
L_0059: blt.s L_0044
L_005b: ret 
            for(int i = 0; i < N - Old.Length; i++)
00007FF942690ADD  xor         eax,eax  
            for(int i = 0; i < N - Old.Length; i++)
00007FF942690ADF  mov         ebx,esi  
00007FF942690AE1  sub         ebx,ebp  
00007FF942690AE3  test        ebx,ebx  
00007FF942690AE5  jle         00007FF942690AFF  
                dd[i] = d;
00007FF942690AE7  mov         rdx,qword ptr [rdi]  
00007FF942690AEA  cmp         eax,dword ptr [rdx+8]  
00007FF942690AED  jae         00007FF942690B11  
00007FF942690AEF  movsxd      rcx,eax  
00007FF942690AF2  vmovsd      qword ptr [rdx+rcx*8+10h],xmm6  
            for(int i = 0; i < N - Old.Length; i++)
00007FF942690AF9  inc         eax  
00007FF942690AFB  cmp         ebx,eax  
00007FF942690AFD  jg          00007FF942690AE7  
00007FF942690AFF  vmovaps     xmm6,xmmword ptr [rsp+20h]  
00007FF942690B06  add         rsp,30h  
00007FF942690B0A  pop         rbx  
00007FF942690B0B  pop         rbp  
00007FF942690B0C  pop         rsi  
00007FF942690B0D  pop         rdi  
00007FF942690B0E  pop         r14  
00007FF942690B10  ret