Delete (0) from a TList is expensive because all the subsequent items need to be moved down. If I need to delete a large number of items from the start of an even larger list what's the fastest way?
Deleting a large range of elements from the beginning of a TList
is expensive. Although the class name flatters to deceive, a TList
is in fact an array. In TList
there is no facility to delete a range–each item must be deleted individually and then the rest of the list is moved down. For a large range that's going to provoke an awful lot of reallocations and full list moves.
If you had a more modern Delphi you could use the generic list class TList<T>
and avail yourself of the DeleteRange
method. The documentation includes this vital note:
This is an O(ACount) operation.
In Delphi 2006 you can write something with equivalent performance characteristics like this:
procedure DeleteRange(List: TList; AIndex, ACount: Integer);
var
i: Integer;
NewCount: Integer;
begin
NewCount := List.Count-ACount;
Assert(AIndex>=0);
Assert(ACount>=0);
Assert(NewCount>=0);
for i := AIndex to NewCount-1 do
List[i] := List[i+ACount]
List.Count := NewCount;
end;
Delete(0)
vs. Delete(Count-1)
is of course a difference. By reindexing the pointers stored in a list and deleting from the end of a list you will save the memory movements. Maybe I should go to sleep last night :) Sorry and +1 –
Waxplant As Marcelo already said, you could copy down the whole block, but instead of doing that item by item, you could move the entire with one call to Move(), using TList.List
as an argument.
Do note that TList.List
was a PPointerList
(^TPointerList; TPointerList = array[0..MaxListSize - 1] of Pointer;
) in older Delphi versions and became a TPointerList
(TPointerList = array of Pointer;
) in Delphi XE2, so you should use the correct indirection:
TList(aList).List^[index] // for older Delphi's
and
TList(aList).List[index] // for Delphi XE2
TList.Notify
, but that might not matter –
Carroll Here's how you do it in XE2, since TList is an array of pointers.
The implementation will be similar on Delphi 2006, but I can't write the code since I don't have 2006.
// I have 1000000 items, and I want to delete the first 5000
// Copy the pointer array items up the array
CopyMemory(@myList.List[0],
@myList.List[5000],
SizeOf(Pointer) * (myList.Count - 5000));
// Reset the count. Delphi cooperates as long as we're shrinking
myList.Count := myList.Count - 5000;
// You now have tons of unused memory at the end, it's fine
// but if you want to clean house
myList.Capacity := myList.Count;
As long as all of the things the pointers point to are memory managed elsewhere, there's no leak.
Here's the proof:
type
TMyObject = class
index: Integer;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
myList: TList;
i: Integer;
myObject: TMyObject;
begin
// Create a list with 1000000 entries
myList := TList.Create;
for i := 1 to 1000000 do
begin
myObject := TMyObject.Create;
myObject.index := i;
myList.Add(myObject);
end;
// Delete the first 5000
CopyMemory(@myList.List[0],
@myList.List[5000],
SizeOf(Pointer) * (myList.Count - 5000));
myList.Count := myList.Count - 5000;
myList.Capacity := myList.Count;
// Show the new count
ShowMessage(IntToStr(myList.Count));
// Shows that my array now has objects 5001 and up
for i := 0 to 5 do
begin
myObject := TMyObject(myList.Items[i]);
ShowMessage(IntToStr(myObject.index));
end;
end;
The proof has major memory leaks, but we created the objects just to show the values.
If order matters, and you have N items to remove at the front:
for I := 0 to List.Count - N - 1 do
list[I] := list[I + N];
for I := list.Count - 1 downto list.Count - N do
list.Delete(I)
I haven't thought this code through very well, so you'll need to check for off-by-one errors and the like.
If order doesn't matter, you can simply exchange the last N items with the first N items, and the delete the last N items as above.
List.Count := ...
–
Carroll Here's a thought : If you know all items in your List are assigned, you could nil the ones you want to remove and just call TList.Pack(), which figures out where the empty spots are and moves everything else aside as efficiently as possible. This requires a scan through all items though, so it might not be what you want, but it doesn't use Delete (and thus prevents Nitofy calls) also. The implementation of Pack hasn't changed a bit between D2006 and XE2, so you can depend on it's efficiency.
Note, that to nil the items you want removed, you could use List[aIndex] := nil
but that would still impose a Notify() call, so List.List[aIndex] := nil
might be faster for that.
First of all, use BeginUpdate and EndUpdate to prevent updating the UI of the TList by deleting each item.
second: try to delete the items at the most lower one in the list first. In other word, deleting items from down to top the list takes less efficient on other items.
BeginUpdate
or EndUpdate
on TList. –
Carroll TList
isn't a graphic component. And I'm not quite sure what the second paragraph says - can you edit your answer? –
Acton © 2022 - 2024 — McMap. All rights reserved.