有没有一种办法看到theJITter对于给定的C#/ CIL公司生产的天然code?公司生产、办法、CIL、theJITter

2023-09-02 10:47:59 作者:蝶泳奈何桥

在对this回答(这表明使用位移位运算符在整数乘法/除法,出于性能考虑),我问这是否会真正更快。在我的脑海里是一个想法,在部分的水平,一些会聪明地制定出>> 1 / 2 是相同的操作。不过,我现在想知道,其实这是真的,如果是,它会发生什么样的水平。

一个测试程序产生以下的比较CIL(与优化),了解其分别划分和转移他们的说法两种方法:

  IL_0000:ldarg.0
  IL_0001:ldc.i4.2
  IL_0002:DIV
  IL_0003:RET
法计划} //结束::分频器
 
无实验,无数据没关系,用公共数据库发自己的文章

  IL_0000:ldarg.0
  IL_0001:ldc.i4.1
  IL_0002:SHR
  IL_0003:RET
法计划} //结束::移位
 

因此​​,C#编译器发出 DIV SHR 的说明,而不聪明。我现在想看到抖动产生的实际x86汇编,但我不知道如何做到这一点。它甚至有可能?

修改导入

结果

感谢您的回答,已经接受了nobugz的一个,因为它包含有关调试选项的关键信息。是什么最终为我工作是:

切换到发布配置 在工具|选项​​|调试器,关闭模块负载燮preSS JIT优化(即我们想要的允许的JIT优化) 在同一个地方,关掉启用仅我的code'(即我们要调试的所有的code) 将一个 Debugger.Break()语句的地方 在生成组装 运行.exe文件,当它打破,调试使用现有的VS实例 现在,反汇编窗口会显示实际的x86,那将被执行

结果是启发,至少可以说 - 原来抖动实际上可以做算术!这里的编辑样本反汇编窗口。各种 -Shifter 方法除以使用二的幂>> ;各 -Divider 方法除以使用整数 /

  Console.WriteLine(的String.Format(
     {0}
     换挡除以2:{1}
     除以除以2:{2},
     60,TwoShifter(60),TwoDivider(60)));

00000026 MOV DWORD PTR [EDX + 4],三通遥控
...
0000003b MOV DWORD PTR [EDX + 4],1EH
...
00000057 MOV DWORD PTR [ESI + 4],1EH
 

无论静态除以2的方法不仅被内联,但实际计算已经完成了抖动

  Console.WriteLine(的String.Format(
    {0}
    除以除以3:{1},
    60,ThreeDivider(60)));

00000085 MOV DWORD PTR [ESI + 4],三通遥控
...
000000a0 MOV DWORD PTR [ESI + 4],14H
 

同样的,静态除以3。

  Console.WriteLine(的String.Format(
    {0}
    换挡除以4:{1}
    除以除以4 {2},
    60,FourShifter(60),FourDivider(60)));

000000CE MOV DWORD PTR [ESI + 4],三通遥控
...
000000e3 MOV DWORD PTR [EDX + 4]的0Fh
...
000000ff MOV DWORD PTR [ESI + 4]的0Fh
 

和静态除以4。

最好的:

  Console.WriteLine(的String.Format(
    {0}
    正除以2:{1}
    正除以3:{2}
    正除以4:{3},
    60,分频器(60,2),分频器(60,3),分频器(60,4)));

0000013e MOV DWORD PTR [ESI + 4],三通遥控
...
0000015b MOV DWORD PTR [ESI + 4],1EH
...
0000017b MOV DWORD PTR [ESI + 4],14H
...
0000019b MOV DWORD PTR [EDI + 4]的0Fh
 

它内联,然后计算出所有这些静态分裂!

但是如果结果不是静态的?我加入到code读取从控制台的一个整数。这就是它产生的分歧上:

  Console.WriteLine(的String.Format(
    {0}
    换挡除以2:{1}
    除以除以2:{2},
    I,TwoShifter(i)中,TwoDivider(ⅰ)));

00000211特区EAX,1
...
00000230特区EAX,1
 

所以尽管CIL是不同的,抖动知道,再除以2是右移1。

  Console.WriteLine(的String.Format(
    {0}
    除以除以3:{1},我,ThreeDivider(ⅰ)));
 

00000283 IDIV EAX,ECX

和它知道你要除以3分。

  Console.WriteLine(的String.Format(
    {0}
    换挡除以4:{1}
    除以除以4 {2},
    I,FourShifter(i)中,FourDivider(ⅰ)));

000002c5特区EAX,2
...
000002ec特区EAX,2
 

和它知道,除以4是右移2。

最后(最好的一次!)

  Console.WriteLine(的String.Format(
    {0}
    正除以2:{1}
    正除以3:{2}
    正除以4:{3},
    I,分频器(1,2),分频器(ⅰ,3),分频器(ⅰ,4)));

00000345特区EAX,1
...
00000370 IDIV EAX,ECX
...
00000395特区ESI,2
 

据内联的方式,制定了做事情的最好方法的基础上,静态可用参数。尼斯。

所以,是的,在C#和x86,东西之间的协议栈的某处的是的聪明来制定出>> 1 / 2 是相同的。而所有这一切给了更多的重量在我心中我认为,加在一起的C#编译器,抖动,和CLR做出的一大堆更多克利夫研究比任何小动作,我们可以尝试卑微应用程序的程序员:)

解决方案

您不会得到有意义的结果,直到你配置调试器。工具+选项,调试,通用,关闭模块负载燮preSS JIT优化。切换到发布模式配置。一个示例代码段:

 静态无效的主要(字串[] args){
  int值= 4;
  INT结果= divideby2(值);
}
 

您这样做是正确的,如果拆卸看起来是这样的:

  00000000 RET
 

您将不得不愚弄JIT优化,迫使前pression进行评估。使用Console.WriteLine(变量)可以提供帮助。然后,你应该看到这样的内容:

  0000000A MOV EDX,2
0000000F MOV EAX,DWORD PTR [ECX]
00000011电话DWORD PTR [EAX + 000000BCh]
 

是的,它评估的结果在编译时。作品pretty的好,不是么。

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. 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:

  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. I would now like to see the actual x86 assembler that the JITter produces, but I have no idea how to do this. 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. What eventually worked for me is:

Switch to Release configuration In Tools | Options | Debugger, switch off 'Suppress JIT optimization on module load' (ie we want to allow JIT optimization) Same place, switch off 'Enable Just My Code' (ie we want to debug all code) Put a Debugger.Break() statement somewhere Build the assembly Run the .exe, and when it breaks, debug using the existing VS instance Now the Disassembly window shows you the actual x86 that's going to be executed

The results were enlightening to say the least - it turns out the JITter can actually do arithmetic! Here's edited samples from the Disassembly window. The various -Shifter methods divide by powers of two using >>; the various -Divider methods divide by integers using /

 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

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.

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.

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.

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

00000283 idiv eax,ecx

And it knows you have to divide to divide by 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.

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. 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 clever than any little tricks we can try as humble applications programmers :)

解决方案

You won't get meaningful results until you configure the debugger. Tools + Options, Debugging, General, turn off "Suppress JIT optimization on module load". 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. Using Console.WriteLine(variable) can help. 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.