Andy’s Blog and Tools

Syndicate content
Delphi, C++Builder and other thoughts
Updated: 1 hour 44 min ago

What’s wrong with virtual methods called through an interface

Tue, 05/31/2016 - 11:20

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

UPDATE: As fast as this implementation may be, it has a problem. As Allen and Mark pointed out, it accesses memory on the stack that is treated as free memory from the system. So if a hardware interrupt is triggered between the “add esp,$04” and the “jmp [esp-$08]”, the data on the stack is overwritten and the jump will end somewhere but not where it should be.

UPDATE 2: Thorsten Engler sent me an e-mail that invalidates the “hardware interrupt problem”. All interrupts are handled in kernel mode and kernel mode code doesn’t touch the user stack. The CPU itself switches the SS:ESP before invoking the interrupt handler.

Based on AMD64 Architecture Programmer’s Manual Volume 2 – System Programming Rev.3.22 Section 8.7.3 Interrupt To Higher Privilege:

When a control transfer to an exception or interrupt handler running at a higher privilege occurs (numerically lower CPL value), the processor performs a stack switch using the following steps:

  1. The target CPL is read by the processor from the target code-segment DPL and used as an index into the TSS for selecting the new stack pointer (SS:ESP). For example, if the target CPL is 1, the processor selects the SS:ESP for privilege-level 1 from the TSS.
  2. Pushes the return stack pointer (old SS:ESP) onto the new stack. The SS value is padded with two bytes to form a doubleword.
Categories: News, Blogs, and Tips

System.ByteStrings for 10.1 Berlin

Tue, 05/31/2016 - 03:30

Delphi 10.1 Berlin reintroduces UTF8String and RawByteString for the NextGen compilers (Android, iOS). But ShortString and AnsiString are still missing. The compiler has full support for them but you can’t use them because they are declared with a leading underscore in the System.pas unit what makes them inaccessible because “_” is compiled to “@” what you can’t use for an identifier.

By patching DCU files it is possible to make those hidden types accessible.

The unit System.ByteStrings for 10.1 Berlin reintroduces

  • ShortString
  • AnsiString
  • AnsiChar
  • PAnsiChar
  • PPAnsiChar
  • UTF8String (XE5-10 Seattle)
  • PUTF8String (XE5-10 Seattle)
  • RawByteString (XE5-10 Seattle)
  • PRawByteString (XE5-10 Seattle)

Usage:
Add the System.ByteStrings.dcu’s path to the compiler’s search path and add the unit to your uses clauses.

There is no *.PAS file because the DCU is patched with a hex editor to get access to the hidden types.

Name IDE Version File Size Downloads Added System.ByteStrings XE5 RTM/UP1 only XE5ByteStrings.7z 2.45 KB 981 times 2013-10-23 System.ByteStrings XE5 UP2 only XE5Up2ByteStrings.7z 2.85 KB 886 times 2013-12-20 System.ByteStrings XE6 XE6ByteStrings.7z 2.89 KB 763 times 2014-04-16 System.ByteStrings XE7 XE7ByteStrings.7z 2.89 KB 913 times 2015-01-20 System.ByteStrings XE8 XE8ByteStrings.7z 3.69 KB 974 times 2015-04-16 System.ByteStrings 10 Seattle D10ByteStrings.7z 3.67 KB 923 times 2015-09-01 System.ByteStrings 10.1 Berlin D101ByteStrings.7z 3.72 KB 187 times 2016-05-31
Categories: News, Blogs, and Tips

IDE Fix Pack 5.95 for Delphi 10.1 Berlin

Sun, 05/29/2016 - 16:01

IDE Fix Pack 5.95 supports RAD Studio 10.1 Berlin.

When Windows Defender (or any other anti-virus tool) sees the compiler creating a DCU file and the compiler calls CloseHandle on the file handle, the virus/malware scanner blocks the thread and takes its time to have a look at the file. This causes CloseHandle to take more than 2 milliseconds per file on my system. If you have 2500 units this sums up to 5 seconds. With Windows Defender disabled those 5 seconds go back to under 100 milliseconds.
Because the compiler can’t work in those 5 seconds, the IDE Fix Pack now delegates the CloseHandle calls to a background thread. This means Windows Defender can scan the file while the compiler works on the next units without being blocked by the scan.
On my Win32 test project this parallel execution made the rebuild 5 seconds faster. IDE Fix Pack guarantees that all written DCU files are closed before the binary executable is created. So if you have only some units you won’t see much of a speed improvement because the main thread may wait for the background thread to close all remaining files.

Changelog:

  • Added: RAD Studio 10.1 Berlin support
  • Added: CloseHandle for created DCU files is delegated to a background thread. Windows Defender “workaround”
  • Added: Fix for RSP-14557: DynArraySetLength – resizing an array of managed type is causing entire copy instead of realloc (D10.1, only the IDE)
  • Added: Fix for RSP-13116: TCustomImageList.BeginUpdate/EndUpdate (D10.0)

Download:

Name IDE Version File Size Downloads Added IDE Fix Pack 5.95 2009 (UP4) IDEFixPack2009Reg595.7z 180.99 KB 279 times 2016-05-30 IDE Fix Pack 5.95 (unsupported) 2010 (UP5) IDEFixPack2010Reg595.7z 177.23 KB 295 times 2016-05-30 IDE Fix Pack 5.95 XE (UP1) IDEFixPackXEReg595.7z 163.4 KB 186 times 2016-05-30 IDE Fix Pack 5.95 (unsupported) XE2 (UP4+HF1) IDEFixPackXE2Reg595.7z 237.48 KB 136 times 2016-05-30 IDE Fix Pack 5.95 (unsupported) XE3 (UP2) IDEFixPackXE3Reg595.7z 191.69 KB 115 times 2016-05-30 IDE Fix Pack 5.95 (unsupported) XE4 (UP1) IDEFixPackXE4Reg595.7z 195.43 KB 92 times 2016-05-30 IDE Fix Pack 5.95 (unsupported) XE5 (UP2) IDEFixPackXE5Reg595.7z 194.6 KB 113 times 2016-05-30 IDE Fix Pack 5.95 (unsupported) XE6 (UP1) IDEFixPackXE6Reg595.7z 323.56 KB 121 times 2016-05-30 IDE Fix Pack 5.95 XE7 (UP1) IDEFixPackXE7Reg595.7z 339.73 KB 266 times 2016-05-30 IDE Fix Pack 5.95 XE8 (UP1) IDEFixPackXE8Reg595.7z 337.01 KB 197 times 2016-05-30 IDE Fix Pack 5.95 10 Seattle (RTM/UP1) IDEFixPackD10Reg595.7z 344.75 KB 675 times 2016-05-30 IDE Fix Pack 5.95 10.1 Berlin IDEFixPackD101Reg595.7z 341.28 KB 1632 times 2016-05-30

Download (fastdcc):

Name IDE Version File Size Downloads Added fastdcc 5.95 2009 (UP4) fastdcc2009v595.7z 78.21 KB 48 times 2016-05-30 fastdcc 5.95 (unsupported) 2010 (UP5) fastdcc2010v595.7z 85.26 KB 55 times 2016-05-30 fastdcc 5.95 XE (UP1) fastdccXEv595.7z 87.18 KB 62 times 2016-05-30 fastdcc 5.95 (unsupported) XE2 (UP4+HF1) fastdccXE2v595.7z 109.87 KB 50 times 2016-05-30 fastdcc 5.95 (unsupported) XE3 (UP2) fastdccXE3v595.7z 124.02 KB 43 times 2016-05-30 fastdcc 5.95 (unsupported) XE4 (UP1) fastdccXE4v595.7z 124.17 KB 33 times 2016-05-30 fastdcc 5.95 (unsupported) XE5 (UP2) fastdccXE5v595.7z 125.57 KB 42 times 2016-05-30 fastdcc 5.95 (unsupported) XE6 (UP1) fastdccXE6v595.7z 153.72 KB 38 times 2016-05-30 fastdcc 5.95 XE7 (UP1) fastdccXE7v595.7z 165.99 KB 71 times 2016-05-30 fastdcc 5.95 XE8 (UP1) fastdccXE8v595.7z 166.1 KB 51 times 2016-05-30 fastdcc 5.95 10 Seattle (RTM/UP1) fastdccD10v595.7z 166.25 KB 153 times 2016-05-30 fastdcc 5.95 10.1 Berlin fastdccD101v595.7z 163.74 KB 397 times 2016-05-30

There is also a new IDE Fix Pack 6.0 Beta 3 that contains all the above and the experimental 64 bit compiler performance optimizations.

Download:

Name IDE Version File Size Downloads Added IDE Fix Pack 6.0beta3 (unsupported) XE2 (UP4+HF1) IDEFixPackXE2Reg60beta3.7z 245.08 KB 28 times 2016-05-30 IDE Fix Pack 6.0beta3 (unsupported) XE3 (UP2) IDEFixPackXE3Reg60beta3.7z 199.63 KB 19 times 2016-05-30 IDE Fix Pack 6.0beta3 (unsupported) XE4 (UP1) IDEFixPackXE4Reg60beta3.7z 202.2 KB 13 times 2016-05-30 IDE Fix Pack 6.0beta3 (unsupported) XE5 (UP2) IDEFixPackXE5Reg60beta3.7z 201.25 KB 25 times 2016-05-30 IDE Fix Pack 6.0beta3 (unsupported) XE6 (UP1) IDEFixPackXE6Reg60beta3.7z 343.08 KB 21 times 2016-05-30 IDE Fix Pack 6.0beta3 XE7 (UP1) IDEFixPackXE7Reg60beta3.7z 359.5 KB 34 times 2016-05-30 IDE Fix Pack 6.0beta3 XE8 (UP1) IDEFixPackXE8Reg60beta3.7z 356.88 KB 26 times 2016-05-30 IDE Fix Pack 6.0beta3 10 Seattle (RTM/UP1) IDEFixPackD10Reg60beta3.7z 364.08 KB 100 times 2016-05-30 IDE Fix Pack 6.0beta3 10.1 Berlin IDEFixPackD101Reg60beta3.7z 361.14 KB 183 times 2016-05-30 fastdcc 6.0beta3 (unsupported) XE2 (UP4+HF1) fastdccXE2v60beta3.7z 123.57 KB 18 times 2016-05-30 fastdcc 6.0beta3 (unsupported) XE3 (UP2) fastdccXE3v60beta3.7z 131.54 KB 17 times 2016-05-30 fastdcc 6.0beta3 (unsupported) XE4 (UP1) fastdccXE4v60beta3.7z 134.18 KB 13 times 2016-05-30 fastdcc 6.0beta3 (unsupported) XE5 (UP2) fastdccXE5v60beta3.7z 133.49 KB 18 times 2016-05-30 fastdcc 6.0beta3 (unsupported) XE6 (UP1) fastdccXE6v60beta3.7z 170.29 KB 16 times 2016-05-30 fastdcc 6.0beta3 XE7 (UP1) fastdccXE7v60beta3.7z 183.56 KB 28 times 2016-05-30 fastdcc 6.0beta3 XE8 (UP1) fastdccXE8v60beta3.7z 183.09 KB 25 times 2016-05-30 fastdcc 6.0beta3 10 Seattle (RTM/UP1) fastdccD10v60beta3.7z 183.25 KB 66 times 2016-05-30 fastdcc 6.0beta3 10.1 Berlin fastdccD101v60beta3.7z 182.31 KB 157 times 2016-05-30
Categories: News, Blogs, and Tips

DDevExtensions and DFMCheck for 10.1 Berlin

Sun, 05/29/2016 - 07:11

The DDevExtensions and the DFMCheck IDE plugins are now available for 10.1 Berlin.

Download:

Name IDE Version File Size Downloads Added DDevExtensions 1.61 5-2007 DDevExtensions161Setup.zip 734.07 KB 16578 times 2009-01-10 DDevExtensions 2.8 Features PDF DDevExtensionsFeatures.pdf 602.92 KB 7764 times 2014-12-27 DDevExtensions 2.4 7, 2007 DDevExtensions24Setup7_2007.zip 535.41 KB 8552 times 2011-07-25 DDevExtensions 2.6 (legacy) XE2+UP3 DDevExtensionsSetup26.zip 800.68 KB 1042 times 2013-11-28 DDevExtensions 2.84 2009-10.1 Berlin DDevExtensions284.7z 1.1 MB 952 times 2016-05-28 Name IDE Version File Size Downloads Added DFMCheck 1.6 5-10.1 Berlin DfmCheckSetup16.7z 701.2 KB 326 times 2016-05-28

DDevExtensions Changelog:

  • Version 2.84 (2016-05-28)
    • Added: TAB key works like ENTER in the CodeInsight window.
    • Added: 10.1 Berlin support
Categories: News, Blogs, and Tips