我编写了2个程序,一个在Delphi中,一个在Java中,用于字符串连接,我注意到Delphi中的字符串连接比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");
Stopwatch.Start;
for i := 1 to 50000 do
str := str + 'abc';
Stopwatch.Stop;
ShowMessage('Time in ms: ' + IntToStr(Stopwatch.ElapsedMilliseconds));
两者都以毫秒为单位测量时间,但Delphi程序的速度要快1ms,而Javas则为2秒。为什么Delphi中字符串连接的速度要快得多?
编辑:回顾这个问题并带来更多经验我应该得出结论,主要区别在于Delphi被编译和Java被编译然后在JVM中运行。
答案 0 :(得分:1)
可能还有其他因素,但肯定有很大的贡献者可能是Delphi的默认内存管理器。它的设计有点浪费空间,以减少重新分配内存的频率。
如果你有一个直接的内存管理器(你甚至可能称之为'天真'),你的循环连接字符串实际上更像是:
//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;
请注意,在每次迭代时都会增加分配。如果你运气不好,你经常需要:
然而,Delphi已经从早期使用的默认内存管理器切换到以前的第三方(FastMM),其设计运行速度主要是:
当然,您编写的用于演示性能的简单应用程序可以从新的内存管理器中获益。您运行一个循环,在每次迭代时逐步重新分配字符串。希望尽可能多的就地分配。
您可以通过在循环中强制执行其他分配来尝试绕过FastMM的某些性能改进。 (虽然页面的子分配仍然有效。)
最简单的方法是尝试使用较旧的Delphi编译器(如D5)来证明这一点。
你说你“不想使用String Builder”。但是,我想指出字符串构建器获得类似的好处。具体而言(如果按预期实现):字符串构建器不需要一直重新分配子字符串。到了最后建立字符串的时候了;可以在一个步骤中分配正确的内存量,并将“构建的字符串”的所有部分复制到它们所属的位置。
答案 1 :(得分:0)
在Java(和C#)中,字符串是不可变对象。这意味着,如果你有:
string s =“String 1”;
然后编译器为此字符串分配内存。然后是避风港
s = s + " String 2"
按预期给我们“String 1 String 2”,但由于字符串的不变性,分配了一个新字符串,其大小完全包含“String 1 String 2”,并且两个字符串的内容都被复制到新的位置。然后垃圾收集器删除原始字符串。在Delphi中,字符串更像是“写入时复制”并且引用计数,这要快得多。
C#和Java有类StringBuilder
,其行为与Delphi字符串非常相似,在修改和操作字符串时速度更快。