【发布时间】:2013-09-30 15:05:28
【问题描述】:
我有一个动态分配的整数数组,我想在任意位置插入整数。许多整数如超过 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。)
使用临时列表并使用 Move 而不是 for 循环来移动数据已经大大提高了性能,因为它可以防止数组在必须增长时被复制两次(一次在现有数组上的 SetLength 和另一个移动时间)。
但最坏的情况 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/4 的条目必须移动而不是现在的 1/2。
另一种选择是实现稀疏数组。我记得在 1990 年代的某个商业库中看到过这样的实现,但不记得是哪个(TurboPower?)。
这个过程是一些排序和索引代码的核心,这些代码适用于不同大小的数组,从几十个条目到上面提到的数百万个条目。
目前程序运行大约 2 小时(在我的优化之前它接近 5 小时),我已经知道数组中的条目数将至少增加一倍。随着插入性能越差,数组已经越大,我怀疑条目数量增加一倍时,运行时间至少会增加四倍。
我想要一些关于如何调整性能的建议。内存消耗目前不是什么大问题,但运行时间肯定是。
(这是 Delphi 2007,但除非较新的 Delphi 版本已经具有用于执行上述操作的优化库。Classes.TList 未优化。)
Edit1:刚刚找到我上面提到的稀疏数组实现:它是 TurboPower SysTools 的 StColl。
Edit2:好的,一些背景知识:我的程序读取一个包含当前 240 万个条目的 DBase 表,并从这些条目中生成几个新表。新表被规范化并在创建后被索引(出于性能原因,我在插入数据之前不生成索引,相信我,我先尝试过。)。数组是为生成的表提供内部排序的核心代码。新记录只追加到表中,但它们的 RecNo 按排序顺序插入到数组中。
【问题讨论】:
-
请参阅
Improved Sliced Array implementation的@Runner,如果这提供了如何更好地进行排序的任何输入。 -
@LURD:谢谢。我在他写这篇博文时读过(该页面上的第一条评论是我的)但已经忘记了。
-
告诉我们更多关于这个“可插入数组”的用例。可能的解决方案取决于它们。
-
在你的 Edit2 之后,我仍然不确定你是否真的需要一个数组,或者另一个容器会是更好的选择......
-
你可以试试我的NLDSparseList。
标签: arrays delphi delphi-2007 dynamic-arrays