В комментарии к этому ответу (который предлагает использовать операторы сдвига разряда по целочисленному умножению / подразделение для производительности), я запросил, будет ли это на самом деле быстрее. Подсознательно идея, что на некотором уровне, что-то будет достаточно умно для разработки этого >> 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),Debugger.Break()
оператор где-нибудьРезультаты были поучительны по меньшей мере - оказывается, что Дрожание может на самом деле сделать арифметику! Здесь отредактировал образцы из окна 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 делает намного более умным, чем какие-либо небольшие приемы, которые мы можем попробовать как скромные программисты приложений :)
Вы не получите значимых результатов, пока не настроите отладчик. Инструменты + Опции, Отладка, Общие, отключите "Подавление 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, она вычислила результат во время компиляции. Работает неплохо, не так ли?
Да. Для этого в 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
}
Во время отладки (и только во время отладки) просто нажмите на кнопку Debug - Windows - Disassembly или нажмите соответствующую комбинацию клавиш Ctrl+Alt+D.
.