[英]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#编译器发出
div
或shr
指令,而不是聪明。 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 编辑添加
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:
最终对我有用的是:
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优化) Debugger.Break()
statement somewhere Debugger.Break()
语句 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.