1
votes

Problem

I wrote 2 programs, one in Delphi and one in Java, for string concatenation and I noticed a much faster string concatenation in Delphi compared to Java.

Java

String str = new String();
long t0 = System.nanoTime();
for (int i = 0; i < 50000; i++) 
    str += "abc";
long t1 = System.nanoTime();
System.out.println("String + String needed " + (t1 - t0) / 1000000 + "ms");

Delphi

Stopwatch.Start;
for i := 1 to 50000 do
  str := str + 'abc';
Stopwatch.Stop;
ShowMessage('Time in ms: ' + IntToStr(Stopwatch.ElapsedMilliseconds));

Question

Both measure the time in milliseconds but the Delphi program is much faster with 1ms vs. Javas 2 seconds. Why is string concatenation so much faster in Delphi?

Edit: Looking back at this question with more experience I should have come to the conclusion that the main difference comes from Delphi being compiled and Java being compiled and then run in the JVM.

2
Because it is a different language perhaps?Hovercraft Full Of Eels
I'd say: because delphi is compiled in native code and java isn't (but still there's JIT so I may be wrong). But maybe you should show us both codes.Jean-François Fabre
How faster is "faster"? Are you talking about it being 1% or maybe 5% faster, or was it more like ten times faster?Robert Columbia
It depends, what does your code look like? Perhaps your Java code is not working in the most optimal way? Or perhaps because Delphi is 100% native as already mentioned?Jerry Dodge
maybe in java you used String instead of StringBuilderFMiscia

2 Answers

1
votes

TLDR

There may be other factors, but certainly a big contributor is likely to be Delphi's default memory manager. It's designed to be a little wasteful of space in order to reduce how often memory is reallocated.

Considering memory manager overhead

When you have a straight-forward memory manager (you might even call it 'naive'), your loop concatenating strings would actually be more like:

//pseudo-code
for I := 1 to 50000 do
begin
  if CanReallocInPlace(Str) then
    //Great when True; but this might not always be possible.
    ReallocMem(Str, Length(Str) + Length(Txt))
  else
  begin
    AllocMem(NewStr, Length(Str) + Length(Txt))
    Copy(Str, NewStr, Length(Str))
    FreeMem(Str)
  end;
  Copy(Txt, NewStr[Length(NewStr)-Length(Txt)], Length(Txt))
end;

Notice that on every iteration you increase the allocation. And if you're unlucky, you very often have to:

  • Allocate memory in a new location
  • Copy the existing 'string so far'
  • Finally release the old string

Delphi (and FastMM)

However, Delphi has switched from the default memory manager used in it's early days to a previously 3rd party one (FastMM) that's designed run faster primarily by:

  • (1) Using a sub-allocator i.e. getting memory from the OS a 'large' page at a time.
  • Then performing allocations from the page until it runs out.
  • And only then getting another page from the OS.
  • (2) Aggressively allocating more memory than requested (anticipating small growth).
  • Then it becomes more likely the a slightly larger request can be reallocated in-place.
  • These techniques can thought it's not guaranteed increase performance.
  • But it definitely does waste space. (And with unlucky fragmentation, the wastage can be quite severe.)

Conclusion

Certainly the simple app you wrote to demonstrate the performance greatly benefits from the new memory manager. You run through a loop that incrementally reallocates the string on every iteration. Hopefully with as many in-place allocations as possible.

You could attempt to circumvent some of FastMM's performance improvements by forcing additional allocations in the loop. (Though sub-allocation of pages would still be in effect.)
So simplest would be to try an older Delphi compiler (such as D5) to demonstrate the point.

FWIW: String Builders

You said you "don't want to use the String Builder". However, I'd like to point out that a string builder obtains similar benefits. Specifically (if implemented as intended): a string builder wouldn't need to reallocate the substrings all the time. When it comes time to finally build the string; the correct amount of memory can be allocated in a single step, and all portions of the 'built string' copied to where they belong.

0
votes

In Java (and C#) strings are immutable objects. That means that if you have:

string s = "String 1";

then the compiler allocates memory for this string. Haven then

s = s + " String 2"

gives us "String 1 String 2" as expected but because of the immutability of the strings, a new string was allocated, with the exactly size to contain "String 1 String 2" and the content of both strings is copied to the new location. Then the original strings are deleted by the garbage collector. In Delphi a string is more "copy-on-write" and reference counted, which is much faster.

C# and Java have the class StringBuilder with behaves a lot like Delphi strings and are quite faster when modifying and manipulating strings.