What’s wrong with virtual methods called through an interface

  • 主题发起人 主题发起人 Andreas Hausladen
  • 开始时间 开始时间
A

Andreas Hausladen

Unregistered / Unconfirmed
GUEST, unregistred user!
Calling a virtual method through an interface always was a lot slower than calling a static method through an interface. But why is that? Sure, the virtual method call costs some time, but comparing it with the difference of a normal static and virtual method call shows that the timings diverge too much.

i7-4790 3.6GHz
10,000,000 calls to empty method
Instance call Interface call
Static method 12 ms 17 ms
Virtual method 17 ms 164 ms

Let’s assume we have this declaration:


type
IMyInterface = interface
procedure Test(A, B: Integer);
end;
TTest = class(TInterfacedObject, IMyInterface)
public
procedure Test(A, B: Integer); virtual;
end;


The compiler will generate a helper function for the interface method “Test”. This helper converts the “MyIntf” interface reference in the call “MyIntf.Test()” to the object reference behind the interface and then jumps to the virtual method.


add eax,-$0C // convert the interface reference to the object reference
push eax // save the object reference on the stack
mov eax,[eax] // access the VMT
mov eax,[eax] // get the “Test” VMT method address
xchg [esp],eax // swap the object ref on the stack with the method address
ret // do the jump to the method address


This is very slow as you can see in the table above. If you know the “XCHG mem,reg” instruction, then you also know that it has an implicit “CPU LOCK” that slows down the method call a lot. But why is it using the XCHG instruction in the first place? Well, we are in between a method call. All the parameters are already loaded in to EAX, EDX and ECX. So we can’t use those to do the swap. The only way is to use the stack as temporary variable, and XCHG seemed to be the choice of the compiler engineer at the time interfaces were introduced to Delphi.

Let’s change that code to not use XCHG.


add eax,-$0 C // convert the interface reference to the object reference
push eax // reserve space for the method address used by RET
push eax // save the object reference on the stack
mov eax,[eax] // access the VMT
mov eax,[eax] // get the “Test” VMT method address
mov [esp+04],eax // write the method address to the reserved space
pop eax // restore the object reference
ret // do the jump to the method address

i7-4790 3.6GHz
10,000,000 calls to empty method
Instance call Interface call
Static method 12 ms 17 ms
Virtual method 17 ms 99 ms
Virtual method (XCHG) 164 ms

This is a lot faster, but still slow compared to the “Instance call”. The helper has a lot of memory accesses, but they shouldn’t slow it that much down, especially not in a tight loop when everything comes from the CPU’s cache.

So where does the code spend the time? Well, modern CPUs (after P1) have a feature called “return stack buffer”. The CPU puts the return address on the “return stack buffer” for every CALL instruction so it can predict where the RET instruction will jump to. This requires that every CALL is matched by a RET. But wait, the helper uses a RET for an indirect jump. We have the CALL from the interface method call, the RET in the helper and the RET in the actual method. That doesn’t match up. In other words, this helper renders the “return stack buffer” invalid what comes with a performance hit because the CPU can’t predict where to jump.

Let’s see what happens if we replace the RET with a JMP.


add eax,-$0C // convert the interface reference to the object reference
push eax // save the object reference on the stack
mov eax,[eax] // access the VMT
push DWORD PTR [eax]// save the “Test” VMT entry method address on the stack
add esp,$04 // skip the method address stack entry
pop eax // restore the object reference
jmp [esp-$08] // jump to the method address

i7-4790 3.6GHz
10,000,000 calls to empty method
Instance call Interface call
Static method 12 ms 17 ms
Virtual method 17 ms 24 ms
Virtual method (RET) 99 ms
Virtual method (XCHG) 164 ms

查看更多...
 

Similar threads

A
回复
0
查看
932
Andreas Hausladen
A
A
回复
0
查看
805
Andreas Hausladen
A
A
回复
0
查看
834
Andreas Hausladen
A
A
回复
0
查看
566
Andreas Hausladen
A
后退
顶部