Есть ли способ увидеть нативный код, созданный JITter для данного C # / CIL? - PullRequest
20 голосов
/ 22 декабря 2009

В комментарии к этому ответу (который предлагает использовать операторы сдвига битов для целочисленного умножения / деления для повышения производительности) я спросил, будет ли это на самом деле быстрее. В глубине души я думаю, что на уровне некоторого что-то будет достаточно умным, чтобы понять, что >> 1 и / 2 - это одна и та же операция. Однако теперь мне интересно, действительно ли это так, и на каком уровне это происходит.

Тестовая программа создает следующий сравнительный CIL (при включенном optimize) для двух методов, которые соответственно делят и сдвигают свой аргумент:

  IL_0000:  ldarg.0
  IL_0001:  ldc.i4.2
  IL_0002:  div
  IL_0003:  ret
} // end of method Program::Divider

против

  IL_0000:  ldarg.0
  IL_0001:  ldc.i4.1
  IL_0002:  shr
  IL_0003:  ret
} // end of method Program::Shifter

Таким образом, компилятор C # выдает команды div или shr, не будучи умным. Теперь я хотел бы увидеть настоящий ассемблер x86, который производит JITter, но я понятия не имею, как это сделать. Это вообще возможно?

изменить добавить

Результаты

Спасибо за ответы, приняли ответ от nobugz, поскольку он содержал ключевую информацию об этой опции отладчика. Что в итоге сработало для меня:

  • Переключение в конфигурацию разблокировки
  • В Tools | Options | Debugger отключить «Подавить оптимизацию JIT при загрузке модуля» (т. Е. Мы хотим разрешить оптимизацию JIT)
  • То же место, отключите «Включить только мой код» (т.е. мы хотим отладить весь код)
  • Положите Debugger.Break() оператор где-то
  • Сборка сборки
  • Запустите .exe, а когда он сломается, отладьте с использованием существующего экземпляра VS
  • Теперь в окне «Разборка» показан фактический x86, который будет выполнен

Результаты были по меньшей мере поучительными - оказывается, JITter действительно может делать арифметику! Вот отредактированные образцы из окна Разборка. Различные методы -Shifter делятся на степени двух с использованием >>; различные методы -Divider делятся на целые числа, используя /

 Console.WriteLine(string.Format("
     {0} 
     shift-divided by 2: {1} 
     divide-divided by 2: {2}", 
     60, TwoShifter(60), TwoDivider(60)));

00000026  mov         dword ptr [edx+4],3Ch 
...
0000003b  mov         dword ptr [edx+4],1Eh 
...
00000057  mov         dword ptr [esi+4],1Eh 

Оба метода статического деления на 2 не только встроены, но и фактические вычисления были выполнены JITter

Console.WriteLine(string.Format("
    {0} 
    divide-divided by 3: {1}", 
    60, ThreeDivider(60)));

00000085  mov         dword ptr [esi+4],3Ch 
...
000000a0  mov         dword ptr [esi+4],14h 

То же самое со статическим делением на 3.

Console.WriteLine(string.Format("
    {0} 
    shift-divided by 4: {1} 
    divide-divided by 4 {2}", 
    60, FourShifter(60), FourDivider(60)));

000000ce  mov         dword ptr [esi+4],3Ch 
...
000000e3  mov         dword ptr [edx+4],0Fh 
...
000000ff  mov         dword ptr [esi+4],0Fh 

И статически-делить на 4.

Лучшие:

Console.WriteLine(string.Format("
    {0} 
    n-divided by 2: {1} 
    n-divided by 3: {2} 
    n-divided by 4: {3}", 
    60, Divider(60, 2), Divider(60, 3), Divider(60, 4)));

0000013e  mov         dword ptr [esi+4],3Ch 
...
0000015b  mov         dword ptr [esi+4],1Eh 
...
0000017b  mov         dword ptr [esi+4],14h 
...
0000019b  mov         dword ptr [edi+4],0Fh 

Он встроен, а затем вычислен все эти статические деления!

Но что, если результат не статичен? Я добавил в код, чтобы прочитать целое число из консоли. Вот что он производит для подразделений на этом:

Console.WriteLine(string.Format("
    {0} 
    shift-divided by 2:  {1} 
    divide-divided by 2: {2}", 
    i, TwoShifter(i), TwoDivider(i)));

00000211  sar         eax,1 
...
00000230  sar         eax,1 

Таким образом, несмотря на различие CIL, JITter знает, что деление на 2 смещается вправо на 1.

Console.WriteLine(string.Format("
    {0} 
    divide-divided by 3: {1}", i, ThreeDivider(i)));

00000283 idiv eax, ecx

И он знает, что нужно делить, чтобы делить на 3.

Console.WriteLine(string.Format("
    {0} 
    shift-divided by 4: {1} 
    divide-divided by 4 {2}", 
    i, FourShifter(i), FourDivider(i)));

000002c5  sar         eax,2 
...
000002ec  sar         eax,2 

И он знает, что деление на 4 сдвигает вправо на 2.

Наконец (снова лучший!)

Console.WriteLine(string.Format("
    {0} 
    n-divided by 2: {1} 
    n-divided by 3: {2} 
    n-divided by 4: {3}", 
    i, Divider(i, 2), Divider(i, 3), Divider(i, 4)));

00000345  sar         eax,1 
...
00000370  idiv        eax,ecx 
...
00000395  sar         esi,2 

Он наметил метод и разработал лучший способ сделать что-либо, основываясь на статически доступных аргументах. Ницца.


Так что да, где-то в стеке между C # и x86 что-то достаточно , чтобы понять, что >> 1 и / 2 одинаковы. И все это придало мне еще больший вес, так как мое мнение о том, что объединение компилятора C #, JITter и CLR делает намного более умным r, чем любые маленькие хитрости, которые мы можем использовать как простые приложения программисты:)

Ответы [ 3 ]

8 голосов
/ 22 декабря 2009

Вы не получите значимых результатов, пока не настроите отладчик. Инструменты + Параметры, Отладка, Общие, отключите «Подавить оптимизацию JIT при загрузке модуля» Переключитесь на конфигурацию режима выпуска. Образец фрагмента:

static void Main(string[] args) {
  int value = 4;
  int result = divideby2(value);
}

Вы делаете все правильно, если разборка выглядит следующим образом:

00000000  ret  

Вам придется обмануть JIT-оптимизатор, чтобы заставить выражение вычисляться. Использование Console.WriteLine (переменная) может помочь. Тогда вы должны увидеть что-то вроде этого:

0000000a  mov         edx,2 
0000000f  mov         eax,dword ptr [ecx] 
00000011  call        dword ptr [eax+000000BCh] 

Да, он оценил результат во время компиляции. Работает довольно хорошо, не правда ли.

3 голосов
/ 22 декабря 2009

Да. Visual Studio имеет встроенный дизассемблер для этого. Вы должны добавить команду в строке меню, хотя. Перейдите в Extras / Customize / Commands (хотя я не знаю, действительно ли они так называются в английской версии) и добавьте команду Dissassembly, которая unter Debugging, где-нибудь в строке меню.

Затем установите точку останова в вашей программе и, когда она сломается, нажмите на эту команду дизассемблирования. VS покажет вам разобранный машинный код.

Пример вывода для метода делителя:

public static int Divider(int intArg)
    {
00000000  push        ebp  
00000001  mov         ebp,esp 
00000003  push        edi  
00000004  push        esi  
00000005  push        ebx  
00000006  sub         esp,34h 
00000009  mov         esi,ecx 
0000000b  lea         edi,[ebp-38h] 
0000000e  mov         ecx,0Bh 
00000013  xor         eax,eax 
00000015  rep stos    dword ptr es:[edi] 
00000017  mov         ecx,esi 
00000019  xor         eax,eax 
0000001b  mov         dword ptr [ebp-1Ch],eax 
0000001e  mov         dword ptr [ebp-3Ch],ecx 
00000021  cmp         dword ptr ds:[00469240h],0 
00000028  je          0000002F 
0000002a  call        6BA09D91 
0000002f  xor         edx,edx 
00000031  mov         dword ptr [ebp-40h],edx 
00000034  nop              
    return intArg / 2;
00000035  mov         eax,dword ptr [ebp-3Ch] 
00000038  sar         eax,1 
0000003a  jns         0000003F 
0000003c  adc         eax,0 
0000003f  mov         dword ptr [ebp-40h],eax 
00000042  nop              
00000043  jmp         00000045 
    }
2 голосов
/ 22 декабря 2009

Во время отладки (и только во время отладки) просто нажмите Отладка - Windows - Разборка или нажмите соответствующую комбинацию клавиш Ctrl + Alt + D.

...