有没有办法查看由JITter为给定的C#/ CIL生成的本机代码?

时间:2009-12-22 11:10:18

标签: c# .net jit cil

在对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#编译器发出divshr指令,而不是聪明。我现在想看看JITter生成的实际x86汇编程序,但我不知道如何执行此操作。它甚至可能吗?

编辑添加

调查结果

感谢您的回答,已接受来自nobugz的答案,因为它包含有关该调试器选项的关键信息。最终对我有用的是:

  • 切换到发布配置
  • Tools | Options | Debugger中,关闭'抑制模块加载时的JIT优化'(即我们希望允许 JIT优化)
  • 同一个地方,关闭'启用我的代码'(即我们要调试所有代码)
  • 在某处放置Debugger.Break()声明
  • 构建程序集
  • 运行.exe,当它崩溃时,使用现有的VS实例进行调试
  • 现在,Disassembly窗口显示了将要执行的实际x86

至少可以说结果很有启发性 - 事实证明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比我们可以尝试的任何小技巧一样简单应用程序员:))

3 个答案:

答案 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.