任意の位置に整数を挿入したい動的に割り当てられた整数の配列があります。250 万を超える多数の整数。
私のコードは現在次のようになっています。
type
TIntegerArr = array of Integer;
var
FCount: Integer;
FSortedList: TIntegerArr;
procedure Insert(_Value: Integer; _InsertPos: integer);
var
OldList: TIntegerArr;
begin
OldList := FSortedList;
if Length(FSortedList) < FCount + 1 then begin
OldList := FSortedList;
FSortedList := nil;
SetLength(FSortedList, FCount + 100);
CopyMemory(@FSortedList[0], @OldList[0], SizeOf(Integer) * _InsertPos);
end;
MoveMemory(@FSortedList[_InsertPos + 1], @OldList[_InsertPos], SizeOf(Integer) * (FCount - _InsertPos));
FSortedList[_InsertPos] := _Value;
Inc(FCount);
end;
(実際のコードは、FSortedList と FCount をフィールドとして持つクラスのメソッドです。)
一時リストを使用し、データを移動するために for ループではなく Move を使用すると、パフォーマンスがすでにかなり改善されました。これは、配列が大きくなる必要があるときに配列が 2 回コピーされるのを防ぐためです (1 回は既存の配列の SetLength で、もう 1 回は Move で)。 )。
ただし、最悪の場合、 Insert(SomeValue, 0) は常にすべての既存の値を移動します。
これまでのところ、配列の先頭にオフセットを導入するという方針に沿って考えていたので、新しい値が先頭に挿入されるたびに既存のすべての値を移動する必要はなく、オフセットが 0 に達したときにのみそれを行うことができました。 :
// simple case: inserting at Position 0:
if FOffset = 0 then begin
// [...] reallocate a new array as above
Move(@FSortedList[100], @OldList, SizeOf(Integer) * _InsertPos);
FOffset := 100;
end;
Dec(FOffset);
FSortedList[FOffset] := _NewValue;
(このコードはテストされておらず、おそらくバグがあります)これはもちろん、挿入ポイントが最初または最後に近いかどうかをチェックするように拡張でき、それに応じて最初または最後の値を1つの位置だけ移動して、平均で1つだけ現在の 1/2 ではなく、/4 のエントリを移動する必要があります。
別のオプションは、スパース配列を実装することです。1990 年代に商用ライブラリでそのような実装を見たのを覚えていますが、それがどれだったかは覚えていません (TurboPower?)。
この手順は、数十のエントリから前述の数百万のエントリまで、さまざまなサイズの配列で機能するソートおよびインデックス コードの中心です。
現在、プログラムは約 2 時間 (最適化前は 5 時間近く) 実行され、配列内のエントリ数が少なくとも 2 倍になることがわかっています。配列が大きくなるほど挿入のパフォーマンスが低下するため、エントリ数が 2 倍になると、実行時間は少なくとも 4 倍になると思います。
パフォーマンスを調整する方法についていくつかの提案をしたいと思います。メモリ消費は現在のところそれほど問題ではありませんが、実行時間は間違いなく問題です。
(これは Delphi 2007 ですが、新しい Delphi バージョンに上記を実行するための最適化されたライブラリが既にある場合を除き、大きな違いはありません。Classes.TList は最適化されていません。)
Edit1: 上記のスパース配列の実装を見つけました: TurboPower SysTools の StColl です。
Edit2: OK、いくつかの背景: 私のプログラムは、現在 240 万のエントリを持つ DBase テーブルを読み取り、これらのエントリからいくつかの新しいテーブルを生成します。新しいテーブルは正規化され、作成後にインデックスが作成されます (パフォーマンス上の理由から、データを挿入する前にインデックスを生成しませんが、最初に試してみました)。配列は、生成されたテーブルの内部ソートを提供するコードの中心部分です。新しいレコードはテーブルに追加されるだけですが、それらの RecNo は並べ替えられた順序で配列に挿入されます。