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

在对这个答案的评论中(建议在整数乘法/除法中使用位移运算符,为了提高性能),我询问这实际上是否会更快。 在我的脑海中有一个想法,在某种程度上 ,某些东西将足够聪明,以确定>> 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 Tools | Options | Debugger Tools | Options | Debugger ,关闭’抑制模块负载上的JIT优化’(即我们希望允许 JIT优化)
  • 同一个地方,关闭’启用我的代码’(即我们要调试所有代码)
  • 在某处放置一个Debugger.Break()语句
  • 构建程序集
  • 运行.exe,当它中断时,使用现有的VS实例进行调试
  • 现在,Disassembly窗口显示了将要执行的实际x86

至少可以说结果很有启发性 – 事实certificateJITter实际上可以算术! 这是来自反汇编窗口的已编辑样本。 各种-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加在一起比我们可以尝试作为简陋的应用程序程序员的任何小技巧更加巧妙 🙂

在配置调试器之前,您将无法获得有意义的结果。 工具+选项,调试,常规,关闭“抑制模块加载时的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] 

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

是。 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 } 

在进行调试时(仅在调试时),只需单击Debug – Windows – Disassembly或按相应的快捷键Ctrl + Alt + D.