.NET JIT потенциальная ошибка?
Следующий код дает разные результаты при запуске выпуска внутри Visual Studio и запускает выпуск вне Visual Studio. Я использую Visual Studio 2008 и настраиваю .NET 3.5. Я также пробовал .NET 3.5 SP1.
При запуске за пределами Visual Studio JIT должен заходить. Либо (а) там что-то тонкое происходит с С#, которое мне не хватает, или (б) JIT действительно ошибается. Я сомневаюсь, что JIT может пойти не так, но у меня заканчиваются другие возможности...
Вывод при работе внутри Visual Studio:
0 0,
0 1,
1 0,
1 1,
Вывод при запуске релиза вне 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();
}
}
}
Ответы
Ответ 1
Это ошибка оптимизатора 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. Кажется маловероятным, что они исправят это в старой версии после этого долго.
Ответ 2
Я считаю, что это в подлинной ошибке компиляции 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
Это похоже на то, что оптимизация пошла плохо для меня...
Ответ 3
Я скопировал ваш код в новое консольное приложение.
- Отладка сборки
- Корректный вывод как с отладчиком, так и без отладчика
- Переключение на выпуск сборки
- Опять же, правильный вывод оба раза
- Создал новую конфигурацию x86 (я запускаю X64 Windows 2008 и использовал "Any CPU" )
- Отладка сборки
- Получил правильный вывод как F5, так и CTRL + F5
- Выпустить сборку
- Корректный вывод с прикрепленным отладчиком
- Нет отладчика - Получен неверный вывод
Так что x86 JIT неправильно генерирует код. Удалили мой исходный текст о переупорядочении петель и т.д. Несколько других ответов здесь подтвердили, что JIT неправильно раскручивает петлю, когда на x86.
Чтобы устранить проблему, вы можете изменить объявление IntVec классу и работать во всех вариантах.
Подумайте, что это нужно для MS Connect....
-1 в Microsoft!