System.Move
适用于无类型指针和字节计数器。System.Copy
并SysUtils.StrLCopy
使用字符串(分别为 Pascal 字符串和 C 字符串)和字符计数器。但是 char 和 byte 是不同的类型,因此当您从字符串/字符上下文移动到指针/字节上下文时 - 您应该重新计算以字节为单位的长度。顺便说一句,索引也是如此,Result [Temp1]
以字符而不是字节计算。并且一直如此。
正确的解决方案不是混合不同星球的公民。如果你想要指针 - 使用指针。如果你想要字符和字符串 - 使用字符和字符串。但不要混合它们!分而治之,始终分开并明确何时使用原始指针以及在何处使用键入的字符串!否则你是在误导自己;
function StringListToDelimitedString
( const AStringList: TStrings; const ADelimiter: String ): String;
var
Str : array of String;
Lengths : array of Integer;
Temp1 : NativeInt;
Count, TotalChars : Integer;
PtrDestination: PByte;
PCurStr: ^String;
CurLen: Integer;
Procedure Add1(const Source: string);
var count: integer; // all context is in bytes, not chars here!
Ptr1, Ptr2: PByte;
begin
if Source = '' then exit;
Ptr1 := @Source[ 1 ];
Ptr2 := @Source[ Length(Source)+1 ];
count := ptr2 - ptr1;
Move( Source[1], PtrDestination^, count);
Inc(PtrDestination, count);
end;
begin // here all context is in chars and typed strings, not bytes
Count := AStringList.Count;
if Count <= 0 then exit('');
SetLength(Str, Count); SetLength(Lengths, Count);
TotalChars := 0;
for Temp1 := 0 to Count - 1 do begin
PCurStr := @Str[ Temp1 ];
PCurStr^ := AStringList[ Temp1 ]; // caching content, avoiding extra .Get(I) calls
CurLen := Length ( PCurStr^ ); // caching length, avoind extra function calls
Lengths[ Temp1 ] := CurLen;
Inc(TotalChars, CurLen);
end;
SetLength ( Result, TotalChars + ( Count-1 )*Length( ADelimiter ) );
PtrDestination := Pointer(Result[1]);
// Calls UniqueString to get a safe pointer - but only once
for Temp1 := Low(Str) to High(Str) do
begin
Add1( Str[ Temp1 ] );
Dec( Count );
if Count > 0 // not last string yet
then Add1( Delimeter );
end;
end;
现在,我认为正确的解决方案是停止发明自行车并使用现成的和经过测试的库,例如。
Str := JclStringList().Add(['Hello1','Hello2','Hello3','Hello4']).Join(';');
或者,如果您确实需要添加分隔符 PAST THE LAST 字符串(通常会小心避免),那么
Str := JclStringList().Add(['Hello1','Hello2','Hello3','Hello4', '']).Join(';');
压缩单个百分比的 CPU 功率的原始声明与原始代码不符。快速指针操作的错觉只是被完全不关心性能的次优代码所掩盖。
function StringListToDelimitedString
( const AStringList: TStringList; const ADelimiter: String ): String;
TStringList
是一类。类实例的创建和删除是昂贵(缓慢)的操作。Delphi 为这些类制作了一个灵活的框架——但速度受到影响。因此,如果您想获得一些额外的速度百分比并以牺牲可靠性和灵活性为代价 - 不要使用类。
DelimiterSize : Byte;
相反,它应该与NativeInt
那里的其他穆勒变量一样。您认为您只是节省了几个字节 - 但您强迫 CPU 使用非本机数据类型并不时插入类型转换。这只不过是一个明确引入的延迟。具有讽刺意味的是,您甚至没有保存这些字节,因为 Delphi 只会多填充三个字节以在 32 位边界上分配下一个变量。那是典型的“内存对齐”优化。
Result := ' ';
这个值永远不会被使用。所以这只是浪费时间。
for Str in AStringList do
这种结构需要实例化TInterfacedObject
并调用其虚拟方法,然后使用全局锁定对其进行引用计数 - 是昂贵(缓慢)的操作。多线程任务加载速度慢了两倍。如果您需要压缩几个百分比的速度 - 您应该避免在 for-in 循环中丢失几十个百分比。这些高级循环方便、可靠且灵活——但他们为此付出了速度。
for Str in AStringList do
然后你做两次。但是您不知道 stringlist 是如何实现的。它获取字符串的效率如何?它甚至可以像 TMemo.Lines 那样将消息传递给另一个进程!因此,您应该尽量减少对该类及其众多内部虚拟成员的所有访问。将所有字符串一次缓存在某个局部变量中,不要每次都获取两次!
Move ( Str [1], Result [Temp1], Temp2 );
现在我们遇到了一个非常有趣的问题——是否有假设的地方可以通过使用指针和字节来获得任何速度优势?打开 CPU 窗口,看看这条线是如何实现的!
字符串是引用计数的!当您这样做时,Str2 := Str1;
不会复制任何数据,而只会复制指针。但是当你开始访问字符串中的真实内存缓冲区时——那个Str[1]
表达式——编译器不能更多地计算引用,所以 Delphi 在这里被迫将引用计数器减少到一个。也就是说,Delphi在这里被迫UniqueString
一遍Str
又一遍地调用Result
;检查System.UniqueString
refcounter,如果它大于 1,则制作字符串的特殊本地副本(将所有数据复制到新分配的特殊缓冲区中)。然后你做一个Move
- 就像 Delphi RTL 做的一样。我无法得到速度的任何优势可能来自哪里?
Move ( ADelimiter [1], Result [Temp1], DelimiterSize )
在这里再次进行相同的操作。它们是昂贵的操作!至少调用了一个额外的过程,最坏的情况是分配新缓冲区并复制所有内容。
恢复:
引用计数字符串和原始指针之间的界限是一个代价高昂的界限,每次越过它 - 就迫使 Delphi 付出代价。
在同一个代码中混合这些边界会一次又一次地付出代价。它还会让您自己感到困惑,您的计数器和索引在哪里引用字节以及它们在哪里引用字符。
Delphi 多年来优化了临时字符串操作。并在那里做得很好。超越 Delphi 是可能的——但您需要非常详细地了解每个 CPU 汇编程序指令——程序中 Pascal 源代码背后的内容。这是一项肮脏而乏味的工作。使用这些可靠和灵活的东西作为 for-in 循环和 TStrings 类将不会有那么奢侈。
最后,您很可能会获得百分之几的速度增益,这是没人会注意到的。但是你会用更难理解、编写、阅读和测试的代码来为此付出代价。那些百分之几的速度值得无法维护的代码吗?我对此表示怀疑。
因此,除非您被迫这样做,否则我的建议是不要浪费您的时间,而只是做通常的事情Str := JclStringList().Add(['Hello1','Hello2','Hello3','Hello4']).Join(';');
可靠性和灵活性几乎总是比纯粹的速度更可取。
很遗憾地告诉你,虽然我对速度优化知之甚少,但我很容易在你的代码中看到一个破坏速度的问题,你打算比 Delphi 本身更快。我的经验是千里之外,甚至试图在弦乐领域超越德尔福。而且我认为您没有任何机会,除了浪费大量时间最终获得比股票更差的性能。