System.Move
型指定されていないポインターとバイトのカウンターで動作します。文字列 (それぞれ Pascal 文字列と C 文字列) と文字のカウンターを操作しますSystem.Copy
。SysUtils.StrLCopy
ただし、char と byte は異なる型であるため、string/char コンテキストから pointers/bytes コンテキストに移動する場合は、char 単位の長さをバイト単位の長さに再計算する必要があります。ちなみに、インデックスについても同じで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 パワーの 1 パーセントを圧迫するという最初の主張は、元のコードには当てはまりません。高速なポインター操作の幻想は、パフォーマンスをまったく気にしない次善のコードによって影が薄くなります。
function StringListToDelimitedString
( const AStringList: TStringList; const ADelimiter: String ): String;
TStringList
クラスです。クラス インスタンスの作成と削除は、コストのかかる (時間がかかる) 操作です。Delphi はこれらのクラスの柔軟なフレームワークを作成しましたが、速度は低下します。したがって、信頼性と柔軟性を犠牲にして速度を数パーセント上げたい場合は、クラスを使用しないでください。
DelimiterSize : Byte;
NativeInt
代わりに、残りの数値変数と同じようにする必要があります。数バイト節約できたと思いますが、CPU にネイティブでないデータ型を使用させ、時々型キャストを挿入させました。これは、明示的に導入された遅延に他なりません。皮肉なことに、これらのバイトを保存していませんでした。Delphi は 32 ビット境界で次の変数を割り当てるために 3 バイトを追加するだけだったからです。これは、典型的な「メモリ アラインメント」の最適化です。
Result := ' ';
この値は使用されません。だから、ただの時間のロスです。
for Str in AStringList do
この構築ではTInterfacedObject
、仮想メソッドをインスタンス化して呼び出してから、グローバル ロックで参照カウントする必要があり、コストがかかる (遅い) 操作です。また、マルチスレッドのタスクロードでは 2 倍遅くなります。数パーセントの速度を絞る必要がある場合は、for-in ループで数十パーセントを失うことを避ける必要があります。これらの高レベルのループは、便利で信頼性が高く、柔軟性がありますが、その代償としてスピードがあります。
for Str in AStringList do
その後、あなたはそれを2回行います。しかし、その文字列リストがどのように実装されているかはわかりません。どのくらい効率的に文字列を取得しますか? TMemo.Lines のように、別のプロセスにメッセージを渡すことさえあります。そのため、そのクラスとその多数の内部仮想メンバーへのすべてのアクセスを最小限に抑える必要があります。すべての文字列をローカル変数に 1 回キャッシュします。それらすべてを 2 回フェッチしないでください。
Move ( Str [1], Result [Temp1], Temp2 );
ここで、非常に興味深い質問にたどり着きました。ポインターとバイトを使用することで、速度の利点を得ることができる仮想的な場所はありますか? CPU ウィンドウを開いて、その行が実際にどのように実装されているかを見てください!
文字列は参照カウントされます! これを行うとStr2 := Str1;
、データはコピーされませんが、ポインターのみがコピーされます。しかし、文字列(そのStr[1]
式)内の実メモリ バッファへのアクセスを開始すると、コンパイラはそれ以上参照をカウントできないため、Delphi はここで参照カウンタを 1 つの SINGLE に減らすことを余儀なくされます。つまり、Delphi はここで をUniqueString
何度も呼び出さなければStr
なりResult
ません。System.UniqueString
refcounter をチェックし、それが 1 より大きい場合は、文字列の特別なローカル コピーを作成します (すべてのデータを新しく割り当てられた特別なバッファにコピーします) 。次に、Move
Delphi RTL が行うのと同じように、次のことを行います。速度の利点がどこから来るのかわかりませんか?
Move ( ADelimiter [1], Result [Temp1], DelimiterSize )
そして、ここでも同じ操作が繰り返されます。そして、それらはコストのかかる操作です。少なくとも余分な手順が呼び出され、最悪の場合、新しいバッファが割り当てられ、すべてのコンテンツがコピーされます。
履歴書:
参照カウント文字列と生のポインタとの間の境界はコストのかかるものであり、境界を越えるたびに、Delphi に代償を払わせます。
これらの境界を同じコードに混在させると、代償が何度も何度も支払われます。また、カウンターとインデックスがバイトを参照する場所と文字を参照する場所を混乱させます。
Delphi は何年もの間、カジュアルな文字列操作を最適化しました。そして、そこでかなり良い仕事をしました。Delphi を凌駕することは可能ですが、プログラムの Pascal ソースの背後にあるものを、各 CPU アセンブラ命令まで、非常に詳細に理解する必要があります。それは汚くて退屈な作業です。信頼性が高く柔軟なものを for-in ループや TStrings クラスとして使用するという贅沢はありません。
最終的には、ほとんどの場合、速度が数パーセント向上しますが、誰も気付かないでしょう。しかし、その代償として、理解、記述、読み取り、およびテストがはるかに困難なコードを使用することになります。これらの数パーセントの速度は、保守不可能なコードに値するでしょうか? 疑わしい。
したがって、強制されない限り、私のアドバイスは、時間を無駄にするのをスキップして、通常の操作を行うことです。Str := JclStringList().Add(['Hello1','Hello2','Hello3','Hello4']).Join(';');
信頼性と柔軟性は、ほとんどの場合、速度よりも優先されます。
申し訳ありませんが、速度の最適化についてはよくわかりませんが、Delphi 自体よりも高速にしようとしているコードで、速度を損なう問題を簡単に見つけました。私の経験は、文字列分野で Delphi を凌駕しようとすることさえ、何マイルも離れています。そして、他の可能性はないと思いますが、最終的に在庫のものよりもパフォーマンスが低下するために多くの時間を無駄にしています.