2010-01-13 1 views
381

Следующий код дает разные результаты при запуске выпуска внутри Visual Studio и запускает выпуск вне Visual Studio. Я использую Visual Studio 2008 и настраиваю .NET 3.5. Я также пробовал .NET 3.5 SP1.Ошибка .NET JIT?

При работе за пределами Visual Studio JIT должен ударить. Либо (а) есть что-то тонкое с C#, которое мне не хватает, или (б) JIT фактически ошибочен. Я сомнительно, что JIT может пойти не так, но я бег из других ...

идей!

Вывода при работе в Visual Studio:

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

Output при запуске выпуска вне Visual Studio:

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

В чем причина?

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(); 
     } 
    } 
} 
+14

+1 - что это за большой вопрос: –

+8

Да, как насчет того, чтобы найти серьезную ошибку в чем-то столь же важном, как .Net JIT - поздравляю! –

+73

Это, как представляется, воспроизводится в моей версии 9 декабря на платформе x86. Я передам его команде джиттера. Благодаря! –

ответ

202

Это JIT оптимизатор ошибка. Это разворачивая внутренний цикл, но не обновляет значение oVec.y правильно:

 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 

Исправлена ​​ошибка исчезает, когда вы позволяете приращение oVec.y до 4, что слишком много звонков раскатать.

Один из способов заключается в следующем:

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

UPDATE: повторно проверены в августе 2012 года, эта ошибка была исправлена ​​в версии 4.0.30319 джиттера. Но все еще присутствует в джиттере v2.0.50727. Кажется маловероятным, что они исправят это в старой версии после этого долго.

+11

+1 Это определенно ошибка. Я опубликовал более или менее тот же ответ, что и редактирование моего ответа, когда это произошло. Кажется, что ответы на вопросы здесь часто бывают расы ... –

+3

+1, определенно ошибка - я мог бы определить условия для ошибки (не сказал, что nobugz нашел это из-за меня, хотя!), но этот (и ваш, Ник, так +1 для вас тоже) показывает, что JIT является виновником. интересно, что оптимизация либо удаляется, либо различается, когда IntVec объявляется как класс. Даже если вы явно инициализируете поля struct до 0 перед циклом, то такое же поведение наблюдается. Nasty! –

+3

@Hans Passant Какой инструмент вы использовали для вывода кода сборки? – 2013-05-23 14:51:09

22

Я скопировал ваш код в новое консольное приложение.

  • отладочных
    • Правильный выход как с отладчиком и без отладчика
  • Switched Освободить Построить
    • Опять же, правильный выход оба раза
  • Создано новая конфигурация x86 (я нахожусь на runnin г X64 для Windows 2008 и с помощью 'Any CPU')
  • отладочных
    • Есть правильный вывод и F5 и CTRL + F5
  • сборки выпуска
    • Правильный выход с прикрепленными Debugger
    • Нет отладчика - Получен неправильный вывод

Так что x86 JIT неправильно генерирует код. Удалили мой исходный текст о переупорядочении циклов и т. Д. Несколько других ответов здесь подтвердили, что JIT неправильно раскручивает петлю, когда на x86.

Чтобы устранить эту проблему, вы можете изменить объявление IntVec классу и работать во всех вариантах.

Думаю, что это должно пойти на MS Connect ....

-1 в Microsoft!

+1

Интересная идея, но, конечно, это не «оптимизация», а очень большая ошибка в компиляторе, если это так? Было бы уже найдено, не так ли? –

+0

Я согласен с тобой. Переупорядочивающие петли, подобные этому, могут вызвать неисчислимые проблемы. На самом деле это кажется еще менее вероятным, потому что для циклов for никогда не может достичь 2. –

+2

Похож на один из этих неприятных гейзенбергов: P – arul

79

Я считаю, что это ошибка подлинной JIT-компиляции. Я бы сообщил об этом Microsoft и посмотрел, что они говорят. Интересно, что я обнаружил, что x64 JIT не имеет такой же проблемы.

Вот мое чтение x86 JIT.

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

Это выглядит как оптимизация испортилось мне ...