20

在对此答案的评论中(建议在整数乘法/除法上使用位移运算符,以提高性能),我质疑这实际上是否会更快。在我的脑海里有一个想法,在某种程度上,有些东西会足够聪明,可以解决这个问题,>> 1并且/ 2是相同的操作。但是,我现在想知道这是否真的是真的,如果是的话,它发生在什么级别。

optimize测试程序为分别划分和移动其参数的两种方法生成以下比较 CIL (with on):

  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中,关闭“Suppress JIT optimization on module load”(即我们要允许JIT 优化)
  • 同一个地方,关闭“仅启用我的代码”(即我们要调试所有代码)
  • Debugger.Break()某处发表声明
  • 构建程序集
  • 运行 .exe,当它中断时,使用现有的 VS 实例进行调试
  • 现在 Disassembly 窗口显示了将要执行的实际 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 加在一起比我们作为谦逊的应用程序程序员可以尝试的任何小技巧更聪明:)

4

3 回答 3

8

在配置调试器之前,您不会得到有意义的结果。工具+选项,调试,常规,关闭“Suppress JIT optimization on module load”。切换到发布模式配置。示例片段:

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

如果反汇编看起来像这样,你就做对了:

00000000  ret  

您必须欺骗 JIT 优化器来强制计算表达式。使用 Console.WriteLine(variable) 可以提供帮助。然后你应该看到这样的东西:

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

是的,它在编译时评估了结果。效果很好,不是吗。

于 2009-12-22T12:01:44.143 回答
3

是的。Visual Studio 有一个内置的反汇编程序来执行此操作。不过,您必须将命令添加到菜单栏中。转到 Extras/Customize/Commands(我不知道它们在英文版中是否真的是这样称呼的)并在菜单栏的某处添加命令 Dissassembly,它位于 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 
    }
于 2009-12-22T11:16:54.670 回答
2

在调试时(并且仅在调试时)只需单击调试 - Windows - 反汇编或按相应的快捷键 Ctrl+Alt+D。

于 2009-12-22T11:55:43.667 回答