Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
243 views
in Technique[技术] by (71.8m points)

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

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...