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