Efficient String Concatenation in Delphi

Apple_WormYou may all know about String concatenation in Delphi, but do you know about the implicit String variables the compiler may create for you?

Along with the implicit variables come implicit exception frames, and a whole lot of hidden stack juggling, which can quickly become hidden complexity bottlenecks.

Looking Innocent

What’s more innocent-looking than a function like this one:

function Apples(nb : Integer) : String;
   Result := IntToStr(nb) + ' apple(s)';

but we all know looks can be deceiving, and sometimes where there are apples, there can be a worm…

Well, here it is, look behind the code at the asm generated for that function:

Project65.dpr.31: begin
005D82B0 55               push ebp
005D82B1 8BEC             mov ebp,esp
005D82B3 6A00             push $00
005D82B5 53               push ebx
005D82B6 56               push esi
005D82B7 8BF2             mov esi,edx
005D82B9 8BD8             mov ebx,eax
005D82BB 33C0             xor eax,eax
005D82BD 55               push ebp
005D82BE 68F8825D00       push $005d82f8
005D82C3 64FF30           push dword ptr fs:[eax]
005D82C6 648920           mov fs:[eax],esp
Project65.dpr.32: Result := IntToStr(nb) + ' apple(s)';
005D82C9 8D55FC           lea edx,[ebp-$04]
005D82CC 8BC3             mov eax,ebx
005D82CE E8BD5FE4FF       call IntToStr
005D82D3 8B55FC           mov edx,[ebp-$04]
005D82D6 8BC6             mov eax,esi
005D82D8 B910835D00       mov ecx,$005d8310
005D82DD E85AF9E2FF       call @UStrCat3
Project65.dpr.33: end;
005D82E2 33C0             xor eax,eax
005D82E4 5A               pop edx
005D82E5 59               pop ecx
005D82E6 59               pop ecx
005D82E7 648910           mov fs:[eax],edx
005D82EA 68FF825D00       push $005d82ff
005D82EF 8D45FC           lea eax,[ebp-$04]
005D82F2 E8E9EAE2FF       call @UStrClr
005D82F7 C3               ret

That was scary eh? Does it matter in terms of performance? Yep, it is about twice slower than the alternative I will give below, and it’ll be even worse in a multi-threaded setting. In the profiler it will often come out as begin/end being the bottleneck points.

All that extra complexity because IntToStr is a function that returns a String, so the compiler needs a temporary variable to store that String. Ans a String is a reference-counted type, so the compiler needs an implicit exception frame (and yes, with ARC objects in NextGen you get the same kind of exception frames).

Next: Looking Stupid

5 thoughts on “Efficient String Concatenation in Delphi

  1. Hello Eric!

    Does DWScript have functions that avoid UStrAsg, LStrAsg etc. which are toxic for multithreaded applications?

  2. Interesting, but this seems like premature optimization and reducing the readability of code just to make a string concatenation faster. How fast is the string concatenation now? It’s hard to imagine a single concatenation becoming a bottleneck.

  3. With D7 there is almost no difference between the two methods.

    But… this is faster.

    function ApplesFormatStr(nb : Integer) : String;
    Result := Format(‘%d apple(s)’,[nb]);

  4. @sam: IntToStr was just picked as an example of a simple function returning a string. Under D7 you should see a difference as well (did you test with FastMM or the default D7 memory manager?)

    @joseph: Of course you should check with a profiler first 😉
    However for readability, this is not so clear-cut, in a more real-world example with longer strings, longer functions names, etc. you’re bound to need line breaks as well, so length-wise, it’ll be similar.
    Yet the longer form with explicit assignment and step-by-step concatenation will be more easily debugable and maintainable (more break point locations, ability to inspect intermediate steps easily, more detailed feedback for crash call stacks, etc.).
    IMHO it’s one of those case where compact code isn’t really more readable, but is definitely less maintainable and less efficient to boot.

Comments are closed.