http://www.azulsystems.com/blog/cliff/2010-04-08-inline-cach...
An old blog post by one of the CLR engineers[1] states:
"We don't inline across virtual calls. The reason for not doing this is that we don't know the final target of the call. We could potentially do better here (for example, if 99% of calls end up in the same target, you can generate code that does a check on the method table of the object the virtual call is going to execute on, if it's not the 99% case, you do a call, else you just execute the inlined code), but unlike the J language, most of the calls in the primary languages we support, are not virtual, so we're not forced to be so aggressive about optimizing this case."
I guess things haven't changed. My testing with the CLR indicates that for best performance, you should make sure your IL is already inlined. The CLR does much better with huge function bodies.
1: http://pastebin.com/98c7Bt7f 2: http://blogs.msdn.com/b/davidnotario/archive/2004/11/01/2503...
See [1] for an example where the CLR fully inlines a virtual call (through an interface, specifically)
The call is most definitely virtual (or dynamic if you prefer that term), not statically-dispatched. It just happens to be performed through an interface. I suspect the CLR optimizes this because interfaces are incredibly common (IEnumerable, etc.)
Do you have any actual examples of making an interface call that gets inlined? This post[1], dated 2004 (later than the MSDN article you referenced) from Eric Gunnerson says:
"all the compiler knows is that it has an IProcessor reference, which could be pointing to any instance of an type that implementes IProcessor. There is therefore no way for the JIT to inline this call - the fact that there is a level of indirection in interfaces prevents it. That's the source of the slowdown."
He goes on to say that Sun does do something since Java makes everything virtual, and the CLR could do it in theory, but doesn't.
I skimmed through the linked article you provided but didn't find any mention of inlining interface method calls. On the excellent performance of virtual/interface calls, it says:
"the combination of caching the virtual method and interface method dispatch mechanisms (the method table and interface map pointers and entries) and spectacularly provident branch prediction enables the processor to do an unrealistically effective job"
1: http://blogs.msdn.com/b/ericgu/archive/2004/03/19/92911.aspx...
Or are you saying the interface call is 5x slower than a virtual call? That definitely isn't right.
In my simple program doing a loop, calling an Add function on an interface, it is definitely making a function call each time. It unrolls 4 times, and loads the function pointer once per iteration - I'd have though it would only load it once overall. Loop is 89 bytes. There is no conditional inside the loop to check for the type.[1]
If I change it to not use the interface (don't cast to the interface type), it's unrolled and inlined. Loop is 34 bytes.[2]
It's the same on 32-bit, except there's no unrolling. The non-virtual loop body is 2 instructions (inc, add). The interface has a push, 3 movs and a call. The virtual one requires two extra movs (to load the function pointer - with an interface the address is embedded as a literal).
Shrug. Maybe it still doesn't work with value types? I started it without VS then broke in with the debugger to get the disassembly.
The loop is doing "y = x.Add(y, i)" where y is a local.
Edit: Aha! Using an interface method (not virtual) and strings, I was able to get inlining. I guess the CLR is still weak in dealing with value types.
1: Start of the loop using an interface:
lea r8d,[rdi-1]
mov rbx,qword ptr [FFEEFE60h]
mov edx,eax
mov rcx,rsi ; rsi is the object pointer
lea r11,[FFEEFE60h] ; I am embarrassed to admit I don't know what r11 is doing
call rbx ; just does lea eax[rdx+r8], ret
; similarly 3 more times then loop
2: Without using the interface, the loop body: lea eax,[r8-1] ; r8's the counter
add ecx,eax
lea edx,[rcx+r8]
lea ecx,[rdx+rax]
lea eax,[r8+2]
add ecx,eax
; then loopAdditionally, it's not always so easy to drop to a low-level language. If your architecture is enormous and complicated, it might be totally unfeasible to change languages for hot parts.
In the vast majority of cases where a virtual function call is actually monomorphic or bimorphic at runtime, the JVM JIT can observe that and potentially inline the method (with an if statement in the bimorphic case). It puts guards around the inlined method and deoptimizes in the event that a newly loaded class renders the optimization incorrect.