2、 從TList開始分析……算法
爲了寫一個更好的性能ISAPI Filter,我須要更快速地從TList中刪除部分連續的Item。好比這樣的一段代碼:數組
var p : pChar = 'abcdefgh';
procedure TestDelFromTList;
var t1 : TList;
i : integer;
maxI : integer;
begin
t1 := tlist.create;
t1.count := 100000;
for i:=0 to t1.count-1 do t1[i] := p;
maxI := t1.count-10-1; //最後一個結點
for i:= maxI downto 10 do t1.delete(i);
//正向刪除
//for i:=10 to t2.count-10-1 do t2.delete(10);
end;
這段代碼是初始化一個100000個結點的List,而後刪除其中的第10個到倒數第10個。這段代碼是逆向的,這樣的刪除速度比較快。若是換成正向刪除(已經註釋掉),則速度就慢得很是多了。函數
這樣的刪除是正常的算法。用測效率的程序測試:逆向刪除算法的耗時是21.66個毫秒,則正向刪除的耗時卻能達到58099.02個毫秒。速度慢了2680倍!!!性能
但這樣就很快了麼?不是!我認爲就算是逆向刪除的速度也並非快的。測試
分析TList這個類的源碼,咱們能夠看到,它是這樣寫的(我加入了註釋):指針
procedure TList.Delete(Index: Integer);
var
Temp: Pointer;
begin
if (Index < 0) or (Index >= FCount) then //斷定Index值是否超界
Error(@SListIndexError, Index);
Temp := Items[Index]; //取待刪除結點
Dec(FCount); //Count減一
if Index < FCount then //將待刪除結點後的Buffer提早
System.Move(FList^[Index + 1], FList^[Index],(FCount - Index) *
SizeOf(Pointer));
if Temp <> nil then //發通告
Notify(Temp, lnDeleted);
end;
因爲在TList類是將所有的結點指針存放在FList這個動態數組的指針中,因此只須要將Index+1以後的內存塊向前移4個字節,即SizeOf(Pointer),便可實現Index結點的刪除。內存
可是,若是使用這樣來刪除成批連續的(N個)結點,則要實現N次system.move()操做,操做的內存塊的大小決定了system.move()操做的耗時,而Index值越小的的結點在FList中越靠前,則system.move()要操做的內存塊也就越大。這就是我認爲上述成批刪除效率不高的緣由,也是正向刪除比逆向刪除的耗時慢了慢了2680倍的緣由。源碼
對於成批刪除,理想的算法是從index+len結點開始位置,向前移動count-index-len個結點,這樣,就可以一次完成所有的結點移動,實現刪除操做。這個思路很是好,至少我認爲是這樣。爲此,我實現了下面的代碼:it
procedure CutList(aList:TList; left,len:integer);
begin
with aList do begin
System.Move(List^[left+len], List^[left], (Count-left-len) *
SizeOf(Pointer));
count := count-len;
end;
end;
這段代碼的功能是在TList.List這個Buffer中,將刪除後的剩餘結點直接移動到Left這個位置上,從而完成所有的移動操做。效率
而後,咱們再設count := count-len;來使用個數減小,從而完成了成批量的刪除。
好的,若是一切正常,算法的速度將大幅度提高!OHHH,美妙的想法!
可是,真的是這樣麼?我再用效率測試程序來測試了一輪,結果是這樣的:
1. 測試數據爲10萬個結點,則逆向刪除算法耗時爲20.56毫秒,CutList()函數耗時9.69毫秒;
2. 測試數據爲100萬個結點,則逆向刪除算法耗時爲209.13毫秒,CutList()函數耗時98.01毫秒。
速度比逆向算法提升了一倍,並且應該注意到,CutList()的耗時仍然隨數據量的增大而等比例的增大!!!而從CutList()函數的實現來看,數據量增大,算法耗時應該只增長極少纔對。
要知道,只加快一倍速度的CutList(),並非我所想要的!!!但爲何CutList()函數得不到更高的性能呢???