.NET JIT potansiyel hatası?


404

Aşağıdaki kod, sürümü Visual Studio içinde çalıştırırken ve sürümü Visual Studio dışında çalıştırırken farklı çıktı verir. Visual Studio 2008 kullanıyorum ve .NET 3.5 hedefliyorum. Ayrıca .NET 3.5 SP1'i de denedim.

Visual Studio dışında çalışırken, JIT tekmelemek gerekir. Ya (a) C # ile eksik devam ince bir şey var ya da (b) JIT aslında hatalı. JIT'in yanlış gidebileceğinden şüpheliyim, ancak diğer olasılıklar tükeniyor ...

Visual Studio içinde çalışırken çıktı:

    0 0,
    0 1,
    1 0,
    1 1,

Visual Studio dışında yayın çalıştırırken çıktı:

    0 2,
    0 2,
    1 2,
    1 2,

Sebebi nedir?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Test
{
    struct IntVec
    {
        public int x;
        public int y;
    }

    interface IDoSomething
    {
        void Do(IntVec o);
    }

    class DoSomething : IDoSomething
    {
        public void Do(IntVec o)
        {
            Console.WriteLine(o.x.ToString() + " " + o.y.ToString()+",");
        }
    }

    class Program
    {
        static void Test(IDoSomething oDoesSomething)
        {
            IntVec oVec = new IntVec();
            for (oVec.x = 0; oVec.x < 2; oVec.x++)
            {
                for (oVec.y = 0; oVec.y < 2; oVec.y++)
                {
                    oDoesSomething.Do(oVec);
                }
            }
        }

        static void Main(string[] args)
        {
            Test(new DoSomething());
            Console.ReadLine();
        }
    }
}

8
Evet - buna ne dersiniz: .Net JIT kadar önemli bir şeyde ciddi bir hata bulmak - tebrikler!
Andras Zoltan

73
Bu, x86'daki 4.0 çerçevesinin 9 Aralık derlememde çoğalıyor gibi görünüyor. Onu jitter ekibine ileteceğim. Teşekkürler!
Eric Lippert

28
Bu aslında bir altın rozeti hak eden çok az sorudan biridir .
Mehrdad Afshari

28
Hepimizin bu soru ile ilgilendiği gerçeği gösteriyor ki, Microsoft .NET .NET JIT hata beklemiyoruz .
Ian Ringrose

2
Hepimiz Microsoft'un endişeyle cevap vermesini bekliyoruz .....
Talha

Yanıtlar:


211

Bu bir JIT optimizer hatası. İç döngüyü açıyor ancak oVec.y değerini düzgün güncellemiyor:

      for (oVec.x = 0; oVec.x < 2; oVec.x++) {
0000000a  xor         esi,esi                         ; oVec.x = 0
        for (oVec.y = 0; oVec.y < 2; oVec.y++) {
0000000c  mov         edi,2                           ; oVec.y = 2, WRONG!
          oDoesSomething.Do(oVec);
00000011  push        edi  
00000012  push        esi  
00000013  mov         ecx,ebx 
00000015  call        dword ptr ds:[00170210h]        ; first unrolled call
0000001b  push        edi                             ; WRONG! does not increment oVec.y
0000001c  push        esi  
0000001d  mov         ecx,ebx 
0000001f  call        dword ptr ds:[00170210h]        ; second unrolled call
      for (oVec.x = 0; oVec.x < 2; oVec.x++) {
00000025  inc         esi  
00000026  cmp         esi,2 
00000029  jl          0000000C 

Hata oVec.y'nin 4'e çıkmasına izin verdiğinizde kaybolur, bu da açmak için çok fazla çağrıdır.

Bir geçici çözüm şudur:

  for (int x = 0; x < 2; x++) {
    for (int y = 0; y < 2; y++) {
      oDoesSomething.Do(new IntVec(x, y));
    }
  }

GÜNCELLEME: Ağustos 2012'de yeniden kontrol edildi, bu hata 4.0.30319 sürümünde düzeltildi. Ancak v2.0.50727 titreşiminde hala var. Bu sürenin ardından eski sürümde bunu düzeltmeleri pek olası görünmüyor.


3
+1, kesinlikle bir hata - Hata koşullarını tanımlamış olabilirim (nobugz'un benim yüzümden bulduğunu söylememekle birlikte!), Ama bu (ve seninki, Nick, yani senin için +1 de) JIT'in suçludur. IntVec bir sınıf olarak bildirildiğinde optimizasyonun kaldırılması veya farklı olması ilginçtir. Döngüden önce yapı alanlarını açıkça 0 olarak başlatsanız bile, aynı davranış görülür. Kötü!
Andras Zoltan

3
@Hans Passant Montaj kodunu çıkarmak için hangi aracı kullandınız?

3
@Joan - Sadece Visual Studio, hata ayıklayıcının Sökme penceresinden kopyala / yapıştır ve elle eklenen yorumlar.
Hans Passant

82

Bunun gerçek bir JIT derleme hatası olduğuna inanıyorum. Microsoft'a bildirir ve ne dediklerini görürdüm. İlginç bir şekilde, x64 JIT'in aynı sorunu olmadığını gördüm.

İşte benim x86 JIT okuma.

// save context
00000000  push        ebp  
00000001  mov         ebp,esp 
00000003  push        edi  
00000004  push        esi  
00000005  push        ebx  

// put oDoesSomething pointer in ebx
00000006  mov         ebx,ecx 

// zero out edi, this will store oVec.y
00000008  xor         edi,edi 

// zero out esi, this will store oVec.x
0000000a  xor         esi,esi 

// NOTE: the inner loop is unrolled here.
// set oVec.y to 2
0000000c  mov         edi,2 

// call oDoesSomething.Do(oVec) -- y is always 2!?!
00000011  push        edi  
00000012  push        esi  
00000013  mov         ecx,ebx 
00000015  call        dword ptr ds:[002F0010h] 

// call oDoesSomething.Do(oVec) -- y is always 2?!?!
0000001b  push        edi  
0000001c  push        esi  
0000001d  mov         ecx,ebx 
0000001f  call        dword ptr ds:[002F0010h] 

// increment oVec.x
00000025  inc         esi  

// loop back to 0000000C if oVec.x < 2
00000026  cmp         esi,2 
00000029  jl          0000000C 

// restore context and return
0000002b  pop         ebx  
0000002c  pop         esi  
0000002d  pop         edi  
0000002e  pop         ebp  
0000002f  ret     

Bu benim için kötü giden bir optimizasyon gibi görünüyor ...


23

Kodunuzu yeni bir Konsol Uygulamasına kopyaladım.

  • Hata Ayıklama Derlemesi
    • Hem hata ayıklayıcı ile hem de hata ayıklayıcı olmadan doğru çıktı
  • Sürüm Oluşturmaya Geçildi
    • Yine, çıktıyı iki kez düzeltin
  • Yeni bir x86 yapılandırması oluşturuldu (X64 Windows 2008 çalıştırıyorum ve 'Any CPU' kullanıyordum)
  • Hata Ayıklama Derlemesi
    • Hem F5 hem de CTRL + F5'te doğru çıktı var
  • Sürüm Oluşturma
    • Hata ayıklayıcı takılıyken doğru çıktı
    • Hata ayıklayıcı yok - Yanlış çıktı var

Yani kodu yanlış üreten x86 JIT. Döngülerin vb. Yeniden sıralanması ile ilgili orijinal metnimi sildim. Buradaki diğer birkaç yanıt, JIT'in x86'da döngüyü yanlış çözdüğünü doğruladı.

Sorunu gidermek için IntVec bildirimini bir sınıfa değiştirebilirsiniz ve tüm tatlarda çalışır.

Bunun MS Connect üzerinde devam etmesi gerektiğini düşünün ....

Microsoft'a -1!


1
İlginç bir fikir, ama elbette bu "optimizasyon" değil, bu durumda derleyicide çok büyük bir hata mı? Şimdiye kadar bulunur muydu?
David M

Size katılıyorum. Döngüleri bu şekilde yeniden sıralamak, anlatılmamış sorunlara neden olabilir. Aslında bu daha az olası görünüyor, çünkü for döngüler hiç 2'ye ulaşamıyor.
Andras Zoltan

2
Bu iğrenç Heisenbugs'dan biri gibi görünüyor: P
arul

OP'nin (veya uygulamasını kullanan herhangi birinin) 32 bit x86 makinesi varsa CPU çalışmaz. Sorun, optimizasyonların etkinleştirildiği x86 JIT'in kötü kod üretmesidir.
Nick Guerrera
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.