Существует ли способ видеть собственный код, произведенный theJITter для данного C# / CIL?

В комментарии к этому ответу (который предлагает использовать операторы сдвига разряда по целочисленному умножению / подразделение для производительности), я запросил, будет ли это на самом деле быстрее. Подсознательно идея, что на некотором уровне, что-то будет достаточно умно для разработки этого >> 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 ассемблер, который производит Дрожание, но я понятия не имею, как сделать это. Это даже возможно?

редактирование для добавления

Результаты

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

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

Результаты были поучительны по меньшей мере - оказывается, что Дрожание может на самом деле сделать арифметику! Здесь отредактировал образцы из окна Disassembly. Различное -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 

Оба statically-divide-by-2 методы были не только встроены, но фактические вычисления, были сделаны Дрожанием

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 

То же с statically-divide-by-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 

И statically-divide-by-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, являющийся отличающимся, Дрожание знает, что деление на 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#, Дрожание, и CLR делает намного более умным, чем какие-либо небольшие приемы, которые мы можем попробовать как скромные программисты приложений :)

20
задан Community 23 May 2017 в 10:32
поделиться

3 ответа

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

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] 

Yup, она вычислила результат во время компиляции. Работает неплохо, не так ли?

8
ответ дан 30 November 2019 в 01:26
поделиться

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

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

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

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 
    }
3
ответ дан 30 November 2019 в 01:26
поделиться

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

.
2
ответ дан 30 November 2019 в 01:26
поделиться
Другие вопросы по тегам:

Похожие вопросы: