Reflection and compiler inlining

  • Thread starter Thread starter Alex Clark
  • Start date Start date
A

Alex Clark

Hi all,

Does anyone know if making calls to
System.Reflection.MethodBase.GetCurrentMethod is reliable in an optimised,
Release Build .NET app? I know the JITter can do some inline optimisations,
and if it does will GetCurrentMethod reflect the calling method (i.e. the
one this method was inlined into), or the containing method? I've Googled
but have found contradicting information, and there is no indication in the
MSDN online help about what to expect (whereas there is for the StackFrame
methods).

Can anyone shed some light on this?

Thanks,
Alex
 
Alex Clark said:
Hi all,

Does anyone know if making calls to
System.Reflection.MethodBase.GetCurrentMethod is reliable in an
optimised, Release Build .NET app? I know the JITter can do some
inline optimisations, and if it does will GetCurrentMethod reflect the
calling method (i.e. the one this method was inlined into), or the
containing method? I've Googled but have found contradicting
information, and there is no indication in the MSDN online help about
what to expect (whereas there is for the StackFrame methods).

Can anyone shed some light on this?

No. Some quick tests produced GetCurrentMethod always returning the
current method, but any extra code added to the routine to try to
confirm if it was inlined (by accessing New StackTrace()) stopped the
method from being inlined.

Removing the call to GetCurrentMethod, and just leaving New
StackTrace().ToString showed the method /was/ then being inlined.

And I tried "hiding" the call to GetCurrentMethod in a delegate to try
to ensure the JIT doesn't just avoid inlining routines that call
GetCurrentMethod.
Thanks,
Alex

Sorry I couldn't help. BTW Remember to use Ctrl+F5 from within VS to
allow the JITter to do its thing without any debugging getting in the
way.

Here's my code just in case it helps someone else confirm or contradict
this:
Module Module1
Sub Main()
Dim fn As Func(Of Object) = AddressOf
System.Reflection.MethodBase.GetCurrentMethod
Dim fn2 As Func(Of Object) = Function() (New StackTrace())
TestRoutine(fn)
CallsTestRoutine(fn)
'CallIt(AddressOf TestRoutine)
'CallIt(AddressOf CallsTestRoutine)
TestRoutine2(fn)
CallsTestRoutine2(fn)
'CallIt(AddressOf TestRoutine2)
'CallIt(AddressOf CallsTestRoutine2)
TestRoutine(fn2)
CallsTestRoutine(fn2)
TestRoutine2(fn2)
CallsTestRoutine2(fn2)
Console.ReadLine()
End Sub
'Private Function TestRoutineExtracted() As System.Reflection.MethodBase
' Console.WriteLine(New StackTrace().ToString)
' Return System.Reflection.MethodBase.GetCurrentMethod()
'End Function
Sub TestRoutine(ByVal fn As Func(Of Object))
'Console.WriteLine(TestRoutineExtracted().Name)
'Console.WriteLine(New StackTrace().ToString)
'Console.WriteLine(System.Reflection.MethodBase.GetCurrentMethod().Name)
Console.WriteLine(fn().ToString)
End Sub
Sub CallsTestRoutine(ByVal fn As Func(Of Object))
TestRoutine(fn)
'Console.WriteLine(System.Reflection.MethodBase.GetCurrentMethod().Name)
'Console.WriteLine(fn().ToString)
End Sub
Sub CallIt(ByVal Fn As Action)
Fn()
End Sub
'Private Function TestRoutine2Extracted() As
System.Reflection.MethodBase
' Console.WriteLine(New StackTrace().ToString)
' Return System.Reflection.MethodBase.GetCurrentMethod()
'End Function
<System.Runtime.CompilerServices.MethodImpl(Runtime.CompilerServices.MethodImplOptions.NoInlining)>
_
Sub TestRoutine2(ByVal fn As Func(Of Object))
'Console.WriteLine(TestRoutine2Extracted().Name)
'Console.WriteLine(New StackTrace().ToString)
'Console.WriteLine(System.Reflection.MethodBase.GetCurrentMethod().Name)
Console.WriteLine(fn().ToString)
End Sub
Sub CallsTestRoutine2(ByVal fn As Func(Of Object))
TestRoutine2(fn)
'Console.WriteLine(System.Reflection.MethodBase.GetCurrentMethod().Name)
'Console.WriteLine(fn().ToString)
End Sub
End Module
 
Does anyone know if making calls to
System.Reflection.MethodBase.GetCurrentMethod is reliable in an optimised,
Release Build .NET app?

Yes, because any method that calls MethodBase.GetCurrentMethod() will not be
inlined. This is because .GetCurrentMethod() includes a StackCrawlMark, a
special magical enum for methods that need to walk the stack (like
..GetCurrentMethod() predictably needs, to look for its caller) that prevents
the caller from being inlined. Trying to be clever by sticking the call in a
delegate like Mark did won't upset this, because methods that call delegates
aren't inlined either.

If for some reason you need methods that use functionality like this to be
inlined, consider using LCG (Lightweight Code Generation) instead. It's very
fiddly and requires a good understanding of IL, but it does allow you to
"hand-optimize" just about anything. Again: only use it if it's necessary,
not because you like the illusion of speed.
 
Thanks Mark, I appreciate the help. I'm still Googling but details on the
JIT and how/when it decides to inline are hard to come by, and oftentimes is
just guesswork by various .NET bloggers. Some imply that the JIT will
intentionally *not* inline methods if they have any metadata-dependent code
in them, such as GetCurrentMethod.

If you're still interested, see "inline" (lol):

Mark Hurd said:
No. Some quick tests produced GetCurrentMethod always returning the
current method, but any extra code added to the routine to try to confirm
if it was inlined (by accessing New StackTrace()) stopped the method from
being inlined.

From what I learned, there appears to be some special 32-byte IL limit for
inlining. If the JITer determines the routine being called to be larger
than that, then it won't inline it. It may well be that GetCurrentMethod
followed by instantiation of the StackTrace breaches that limit.
Removing the call to GetCurrentMethod, and just leaving New
StackTrace().ToString showed the method /was/ then being inlined.

Which might confirm my findings that reflection/metadata calls actually
prohibit inlining, but then it may also be that removing GetCurrentMethod
and just leaving the calls to StackTrace put the IL below the 32 byte limit.

Heisenberg's uncertainty principle, anyone? :-)
Sorry I couldn't help.

On the contrary, a fresh set of eyes (not to mention experimental results)
is always helpful, thanks Mark.

Regards,
Alex
 
Back
Top