2009-12-22 1 views
19

В комментарии к this answer (который предлагает использовать операторы бит-сдвига над целым умножением/делением, для производительности), я спросил, будет ли это на самом деле быстрее. В глубине моего сознания есть идея, что на уровне уровень, что-то будет достаточно умным, чтобы выработать то, что >> 1 и / 2 - это та же операция. Однако теперь мне интересно, действительно ли это так, и если да, то на каком уровне это происходит.Есть ли способ увидеть собственный код, созданный theJITter для данного C#/CIL?

Программа испытаний производит следующий сравнительный 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)
  • То же место, выключить «Включить Just My Code» (т.е. мы хотим отладить все код)
  • Поместите 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, не только были встраиваемые методы, но фактические расчеты были сделаны по джиттеру

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 быть другим, джиттер знает, что деление на 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 

И KNO WS, что разделительный на 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 

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


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

+0

Можете ли вы опубликовать свои выводы на благо всех нас? спасибо :) – flesh

ответ

8

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

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

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

00000000 ret 

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

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

Yup, он оценил результат во время компиляции. Хорошо работает, не так ли.

+0

Почему вы не получите значимых результатов, когда включена функция «Подавление оптимизации JIT при загрузке модуля»? Моя интерпретация заключается в том, что включение этого просто означает, что вы отлаживаете оптимизированный/«освобождающий» собственный код. – Justin

3

Да. Visual Studio имеет встроенный дизассемблер для этого. Однако вам нужно добавить команду в свою панель меню. Перейдите в раздел «Дополнительно»/«Настроить/Команды» (я не знаю, действительно ли они так называются в английской версии) и добавьте команду «Разборки», которая является отладочной отладкой, где-то в строке меню.

Затем установите контрольную точку в своей программе и, когда она сломается, щелкните эту команду «Разборка». 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

Не нужно добавлять эту команду в VS, она находится в меню Debug-> Windows. –

2

Пока вы отладка (и только при отладке) просто нажмите на Debug - Windows - разборке или нажмите соответствующий ярлык Ctrl + Alt + D.

Смежные вопросы