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