I have a dynamically allocated array of integers into which I want to insert integers at arbitrary positions. Many integers as in more than 2.5 million.
My code currently looks like this:
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;
(The real code is a method of a class that has FSortedList and FCount as fields.)
Using a temporary list and using Move rather than a for loop for moving the data improved the performance already quite a lot because it prevents the array from being copied twice when it has to grow (once in SetLength on the existing array and another time with Move).
But the worst case Insert(SomeValue, 0) still always moves all existing values.
So far I was thinking along the lines of introducing an offset at the start of the array so rather than having to move all existing values every time a new value is inserted at the front, I could do that only when the offset reaches 0. E.g.:
// 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;
(This code is untested and probably buggy) This of course can be extended to check whether the insertion point is nearer to the beginning or the end and depending on that move either the first or the last values by one position so that on average only 1/4 of the entries has to be moved rather than 1/2 as it currently is.
Another option would be implementing a sparse array. I remember seeing such an implementation in some commercial library back in the 1990ies but don't remember which it was (TurboPower?).
This procedure is central to some sorting and indexing code which works on arrays of different sizes, from just a few dozen entries up to the above mentioned millions of entries.
Currently the program runs about 2 hours (before my optimizations it was close to 5 hours) and I already know that the number of entries in the array is going to at least double. As insert performance gets worse the larger the array already is, I suspect that with double the number of entries, the run time will at least quadruple.
I would like some suggestions on how to tune the performance. Memory consumption is currently not much of an issue but run time definitely is.
(This is Delphi 2007 but that should not make much of a difference unless newer Delphi versions already have an optimized library for doing the above. Classes.TList is not optimized.)
Edit1: Just found the sparse array implementation I mentioned above: It's StColl from TurboPower SysTools.
Edit2: Ok, some background: My program reads a DBase table with currently 2.4 million entries and generates several new tables from these entries. The new tables are normalized and are indexed after they have been created (For performance reasons I don't generate the indexes before inserting the data, trust me, I tried it first.). The array is the central piece of code that provides internal sorting for the generated tables. New records are only appended to the table, but their RecNo is inserted into the array in sorted order.
Improved Sliced Array implementationby@Runnerif this gives any input how to make sorting better. - LU RD