在对this answer的评论中(建议在整数乘法/除法中使用位移运算符,以提高性能),我询问这实际上是否会更快。在我的脑海中有一个想法,在某些级别,有些东西足够聪明,可以确定>> 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
指令,而不是聪明。我现在想看看JITter生成的实际x86汇编程序,但我不知道如何执行此操作。它甚至可能吗?
编辑添加
感谢您的回答,已接受来自nobugz的答案,因为它包含有关该调试器选项的关键信息。最终对我有用的是:
Tools | Options | Debugger
中,关闭'抑制模块加载时的JIT优化'(即我们希望允许 JIT优化)Debugger.Break()
声明至少可以说结果很有启发性 - 事实证明JITter实际上可以做算术!这是来自“反汇编”窗口的已编辑样本。各种-Shifter
方法使用>>
除以2的幂;各种-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比我们可以尝试的任何小技巧一样简单应用程序员:))
答案 0 :(得分:8)
在配置调试器之前,您将无法获得有意义的结果。工具+选项,调试,常规,关闭“抑制模块加载时的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]
是的,它在编译时评估了结果。效果很好,不是吗。
答案 1 :(得分:3)
是。 Visual Studio有一个内置的反汇编程序来做到这一点。您必须将命令添加到菜单栏。转到Extras / Customize / Commands(我不知道它们是否真的在英文版本中被称为),并在菜单栏的某处添加命令Dissassembly,它是unter Debugging。
然后,在程序中设置断点,当它断开时,单击此反汇编命令。 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
}
答案 2 :(得分:2)
在进行调试时(仅在调试时),只需单击Debug - Windows - Disassembly或按相应的快捷键Ctrl + Alt + D.