简体   繁体   English

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

[英]Is there a way to see the native code produced by theJITter for given C# / CIL?

In a comment on this answer (which suggests using bit-shift operators over integer multiplication / division, for performance), I queried whether this would actually be faster. 在对这个答案的评论中(建议使用位移运算符而不是整数乘法/除法,为了提高性能),我询问这实际上是否会更快。 In the back of my mind is an idea that at some level, something will be clever enough to work out that >> 1 and / 2 are the same operation. 在我的脑海中有一个想法,在某种程度上 ,某些东西将足够聪明,以确定>> 1/ 2是相同的操作。 However, I'm now wondering if this is in fact true, and if it is, at what level it occurs. 但是,我现在想知道这是否真的是真的,如果是,它会发生在什么级别。

A test program produces the following comparative CIL (with optimize on) for two methods that respectively divide and shift their argument: 测试程序为两种方法产生以下比较CIL( optimize开启),这两种方法分别对其参数进行划分和移位:

  IL_0000:  ldarg.0
  IL_0001:  ldc.i4.2
  IL_0002:  div
  IL_0003:  ret
} // end of method Program::Divider

versus

  IL_0000:  ldarg.0
  IL_0001:  ldc.i4.1
  IL_0002:  shr
  IL_0003:  ret
} // end of method Program::Shifter

So the C# compiler is emitting div or shr instructions, without being clever. 所以C#编译器发出divshr指令,而不是聪明。 I would now like to see the actual x86 assembler that the JITter produces, but I have no idea how to do this. 我现在想看看JITter生成的实际x86汇编程序,但我不知道如何执行此操作。 Is it even possible? 它甚至可能吗?

edit to add 编辑添加

Findings 发现

Thanks for answers, have accepted the one from nobugz because it contained the key information about that debugger option. 感谢您的回答,已接受来自nobugz的那个,因为它包含有关该调试器选项的关键信息。 What eventually worked for me is: 最终对我有用的是:

  • Switch to Release configuration 切换到发布配置
  • In Tools | Options | Debugger Tools | Options | Debugger Tools | Options | Debugger Tools | Options | Debugger , switch off 'Suppress JIT optimization on module load' (ie we want to allow JIT optimization) Tools | Options | Debugger ,关闭'抑制模块负载的JIT优化'(即我们希望允许 JIT优化)
  • Same place, switch off 'Enable Just My Code' (ie we want to debug all code) 同一个地方,关闭'启用我的代码'(即我们要调试所有代码)
  • Put a Debugger.Break() statement somewhere 在某处放置一个Debugger.Break()语句
  • Build the assembly 构建程序集
  • Run the .exe, and when it breaks, debug using the existing VS instance 运行.exe,当它中断时,使用现有的VS实例进行调试
  • Now the Disassembly window shows you the actual x86 that's going to be executed 现在,Disassembly窗口显示了将要执行的实际x86

The results were enlightening to say the least - it turns out the JITter can actually do arithmetic! 至少可以说结果很有启发性 - 事实证明JITter实际上可以做算术! Here's edited samples from the Disassembly window. 这是来自“反汇编”窗口的已编辑样本。 The various -Shifter methods divide by powers of two using >> ; 各种-Shifter方法使用>>除以2的幂; the various -Divider methods divide by integers using / 各种-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 

Both statically-divide-by-2 methods have not only been inlined, but the actual computations have been done by the JITter 两种静态除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 

Same with statically-divide-by-3. 与静态除以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 

And statically-divide-by-4. 并且静态除以4。

The best: 最好的:

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 

It's inlined and then computed all these static divisions! 它是内联的,然后计算所有这些静态分区!

But what if the result isn't static? 但是如果结果不是静态的呢? I added to code to read an integer from the Console. 我添加到代码中以从控制台读取整数。 This is what it produces for the divisions on that: 这就是它为此所产生的分歧:

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 

So despite the CIL being different, the JITter knows that dividing by 2 is right-shifting by 1. 因此,尽管CIL不同,但JITter知道除以2是右移1。

Console.WriteLine(string.Format("
    {0} 
    divide-divided by 3: {1}", i, ThreeDivider(i)));

00000283 idiv eax,ecx 00000283 idiv eax,ecx

And it knows you have to divide to divide by 3. 它知道你必须除以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 

And it knows that dividing by 4 is right-shifting by 2. 它知道除以4是右移2。

Finally (the best again!) 最后(最好的!)

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 

It has inlined the method and worked out the best way to do things, based on the statically-available arguments. 它已经内联了方法,并根据静态可用的参数找出了最好的方法。 Nice. 尼斯。


So yes, somewhere in the stack between C# and x86, something is clever enough to work out that >> 1 and / 2 are the same. 所以,是的,在某处C#和86之间的堆栈,什么足够聪明的制定出>> 1/ 2是相同的。 And all this has given even more weight in my mind to my opinion that adding together the C# compiler, the JITter, and the CLR makes a whole lot more cleve r than any little tricks we can try as humble applications programmers :) 所有这些在我的脑海中给予了更多的重视,我认为将C#编译器,JITter和CLR加在一起比我们可以尝试作为简陋的应用程序程序员的任何小技巧更加巧妙 :)

You won't get meaningful results until you configure the debugger. 在配置调试器之前,您将无法获得有意义的结果。 Tools + Options, Debugging, General, turn off "Suppress JIT optimization on module load". 工具+选项,调试,常规,关闭“抑制模块加载时的JIT优化”。 Switch to the Release mode configuration. 切换到发布模式配置。 A sample snippet: 示例代码段:

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

You are doing it right if the disassembly looks like this: 如果反汇编看起来像这样,你就是这样做的:

00000000  ret  

You'll have to fool the JIT optimizer to force the expression to be evaluated. 您将不得不欺骗JIT优化器以强制评估表达式。 Using Console.WriteLine(variable) can help. 使用Console.WriteLine(变量)可以提供帮助。 Then you ought to see something like this: 然后你应该看到这样的事情:

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

Yup, it evaluated the result at compile time. 是的,它在编译时评估了结果。 Works pretty well, doesn't it. 工作得很好,不是吗。

Yes. 是。 Visual Studio has a built in disassembler to do that. Visual Studio有一个内置的反汇编程序来做到这一点。 You have to add the command to your menu bar though. 您必须将命令添加到菜单栏。 Go to Extras/Customize/Commands (I don't know if they are really called that way in the english version though) and add the command Dissassembly, which is unter Debugging, somewhere to your menu bar. 转到Extras / Customize / Commands(我不知道它们是否真的在英文版本中被称为),并在菜单栏的某处添加命令Dissassembly,它是unter Debugging。

Then, set a breakpoint in your program and when it breaks, click on this Disassembly command. 然后,在程序中设置断点,当它断开时,单击此反汇编命令。 VS will show you the disassembled machine code. VS将向您显示反汇编的机器代码。

Example output for a Divider-method: 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.

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM