在这个回答中,有人建议使用位移操作符来代替整数乘除法以提高性能。我在评论中质疑这是否真的会更快。在我的脑海中,有一个想法,即在某个级别上,某些智能的编译器将会很聪明地认为>>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的答案,因为它包含了关于调试选项的关键信息。最终对我有用的是:
1. 切换到“发布”配置 2. 在“工具|选项|调试器”中,关闭“抑制模块加载时的JIT优化”(即我们希望允许JIT优化) 3. 同上,在“启用我的代码”处关闭“启用Just My Code”(即我们要调试所有代码) 4. 在某个地方放置一个
Debugger.Break()
语句
5. 构建程序集
6. 运行.exe文件,当它停止时,使用现有的VS实例进行调试
7. 现在Disassembly窗口会显示实际要执行的x86汇编代码结果非常启发人 - 原来JITter实际上可以执行算术运算!以下是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
两种静态除以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等价于将其右移两位。
最后(再次是最好的!)
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 结合起来比我们作为卑微的应用程序员尝试的任何小技巧都要聪明得多 :)