Yayın ve hata ayıklama modunda kod davranışı neden farklı?


84

Aşağıdaki kodu göz önünde bulundurun:

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

Hata Ayıklama modunda sonuç: 100,100,100,100,100. Ancak Release modunda: 100,100,100,100,0.

Ne oluyor?

.NET framework 4.7.1 ve .NET Core 2.0.0 kullanılarak test edilmiştir.


Visual Studio'nun (veya derleyicinin) hangi sürümünü kullanıyorsunuz?
Styxxy

9
Repro; Console.WriteLine(i);final döngüsüne ( dd[i] = d;) bir eklenmesi onu "düzeltir", bu da bir derleyici hatasını veya JIT hatasını gösterir; IL'ye bakıyor ...
Marc Gravell

@Styxxy, vs2015, 2017'de test edildi ve her .net çerçevesini hedefledi> = 4.5
Ashkan Nourzadeh

Kesinlikle bir böcek. Kaldırırsanız da kaybolur if (dd.Length >= N) return;, bu daha basit bir repro olabilir.
Jeroen Mostert

1
Karşılaştırma elmadan elmaya olduğunda, .Net Framework ve .Net Core için x64 kodgeninin benzer performansa sahip olması şaşırtıcı değildir, çünkü (varsayılan olarak) esasen aynı jit üreten koddur. .Net Framework x86 kod geninin performansını .Net Core'un x86 kod geniyle (2.0'dan beri RyuJit kullanıyor) karşılaştırmak ilginç olurdu. Hala eski jit'in (diğer adıyla Jit32) RyuJit'in bilmediği birkaç numara bildiği durumlar var. Ve bu tür vakalar bulursanız, lütfen bunlar için CoreCLR deposunda sorunları açtığınızdan emin olun.
Andy Ayers

Yanıtlar:


70

Bu bir JIT hatası gibi görünüyor; Şunlarla test ettim:

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

ve Console.WriteLine(i)düzeltmeleri eklemek . Tek IL değişikliği şudur:

// ...
L_0040: ldc.i4.0 
L_0041: stloc.3 
L_0042: br.s L_004d
L_0044: ldarg.0 
L_0045: ldind.ref 
L_0046: ldloc.3 
L_0047: ldloc.1 
L_0048: stelem.r8 
L_0049: ldloc.3 
L_004a: ldc.i4.1 
L_004b: add 
L_004c: stloc.3 
L_004d: ldloc.3 
L_004e: ldarg.1 
L_004f: ldloc.0 
L_0050: ldlen 
L_0051: conv.i4 
L_0052: sub 
L_0053: blt.s L_0044
L_0055: ret 

vs

// ...
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 

Bu tam olarak doğru görünüyor (tek fark, ekstra ldloc.3ve call void [System.Console]System.Console::WriteLine(int32)ve farklı ama eşdeğer bir hedeftir br.s).

Bir JIT düzeltmesine ihtiyacı olacak, sanıyorum.

Çevre:

  • Environment.Version: 4.0.30319.42000
  • <TargetFramework>netcoreapp2.0</TargetFramework>
  • VS: 15.5.0 Önizleme 5.0
  • dotnet --version: 2.1.1

O halde hatayı nereye bildirmelisiniz?
Ashkan Nourzadeh

1
NET tam 4.7.1'de de görüyorum, bu yüzden bu bir RyuJIT hatası değilse şapkamı yerim.
Jeroen Mostert

2
.NET 4.7.1'i yeniden üretemedim, yükledim ve şimdi yeniden üretebilirim.
user3057557

3
@MarcGravell .Net çerçevesi 4.7.1 ve .net Core 2.0.0
Ashkan Nourzadeh

4
@AshkanNourzadeh Dürüst olmak gerekirse muhtemelen buraya kaydederdim ve insanların bunun bir RyuJIT hatası olduğuna inandıklarını vurgulayarak
Marc Gravell

6

Bu gerçekten bir montaj hatası. x64, .net 4.7.1, sürüm yapısı.

demontaj:

            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  

Sorun 00007FF942690AFD adresinde, jg ​​00007FF942690AE7 adresindedir. Ebx (4'ü içeren döngü sonu değeri) eax'tan büyükse (jg), değeri i ise geri atlar. Bu elbette 4 olduğunda başarısız olur, dolayısıyla dizideki son öğeyi yazmaz.

Başarısız, çünkü inc'in i'nin yazmaç değeri (eax, 0x00007FF942690AF9'da) ve sonra 4 ile kontrol ediyor, ancak yine de bu değeri yazması gerekiyor. Hata ayıklama derlemesi bu kodu içerdiğinden (N-Old.Length) optimizasyonunun bir sonucu gibi göründüğü için sorunun tam olarak nerede bulunduğunu belirlemek biraz zordur, ancak sürüm derlemesi bunu önceden hesaplar. Yani bu jit insanların düzeltmesi için;)


2
Bu günlerden birinde, assembly / CPU işlem kodlarını öğrenmek için biraz zaman ayırmam gerekiyor. Belki de safça düşünmeye devam ediyorum "eh, IL okuyabilir ve yazabilirim - onu okşayabilmeliyim" - ama asla buna
alışamam

x64 / x86, tho ile başlamak için en iyi montaj dili değil;) Çok fazla işlem kodu var, bir zamanlar hepsini bilen canlı kimse olmadığını okudum. Doğru olup olmadığından emin değilim ama ilk başta okumak o kadar kolay değil. [] Gibi birkaç basit kural kullanmasına rağmen, kaynak bölümünden önceki hedef ve bu kayıtların tümü ne anlama gelir (al, rax'ın 8 bit bölümü, eax, rax'ın 32 bit bölümüdür vb.). Size temelleri öğretmesi gereken vs tho'ya adım atabilirsiniz. IL işlem kodlarını bildiğiniz için çabucak aldığınıza eminim;)
Frans Bouma
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.