[英]Delphi Optimize IndexOf Function
有人可以幫助我加快我的Delphi函數的速度,而不使用二進制搜索在字節數組中查找值。
我多次調用此函數,是否可以通過匯編對其進行優化?
非常感謝。
function IndexOf(const List: TArray< Byte >; const Value: byte): integer;
var
I: integer;
begin
for I := Low( List ) to High( List ) do begin
if List[ I ] = Value then
Exit ( I );
end;
Result := -1;
end;
數組的長度約為15個項目。
好吧,讓我們考慮一下。 首先,請編輯以下行:
For I := Low( List ) to High( List ) do
(您最后忘記了“做”)。 當我們不進行優化而對其進行編譯時,以下是此循環的匯編代碼:
Unit1.pas.29: If List [I] = Value then
005C5E7A 8B45FC mov eax,[ebp-$04]
005C5E7D 8B55F0 mov edx,[ebp-$10]
005C5E80 8A0410 mov al,[eax+edx]
005C5E83 3A45FB cmp al,[ebp-$05]
005C5E86 7508 jnz $005c5e90
Unit1.pas.30: Exit (I);
005C5E88 8B45F0 mov eax,[ebp-$10]
005C5E8B 8945F4 mov [ebp-$0c],eax
005C5E8E EB0F jmp $005c5e9f
005C5E90 FF45F0 inc dword ptr [ebp-$10]
Unit1.pas.28: For I := Low (List) to High (List) do
005C5E93 FF4DEC dec dword ptr [ebp-$14]
005C5E96 75E2 jnz $005c5e7a
這段代碼遠非最佳:局部變量i
實際上是局部變量,即:它存儲在RAM中的堆棧中(您可以通過[ebp- $ 10]地址看到它,ebp是堆棧指針)。
因此,在每個新的迭代中,我們看到如何將數組的地址加載到eax寄存器(mov eax,[ebp- $ 04])中,然后從堆棧中將i加載到edx寄存器(mov edx,[ebp- $ 10])中,然后在至少將List [i]加載到al寄存器中,該寄存器是eax的低字節(mov al,[eax + edx]),然后將其與再次從內存而不是寄存器中獲取的參數'Value'進行比較!
此實現非常慢。
但是最后讓我們打開優化! 這是在項目選項->編譯->代碼生成中完成的。 讓我們看一下新代碼:
Unit1.pas.29: If List [I] = Value then
005C5E5A 3A1408 cmp dl,[eax+ecx]
005C5E5D 7504 jnz $005c5e63
Unit1.pas.30: Exit (I);
005C5E5F 8BC1 mov eax,ecx
005C5E61 5E pop esi
005C5E62 C3 ret
005C5E63 41 inc ecx
Unit1.pas.28: For I := Low (List) to High (List) do
005C5E64 4E dec esi
005C5E65 75F3 jnz $005c5e5a
現在只有4行代碼會一遍又一遍地重復。
值存儲在dl寄存器(edx寄存器的低字節)內部,數組第0個元素的地址存儲在eax寄存器中,i存儲在ecx寄存器中。
因此,“ if List [i] = Value”行僅轉換為1條裝配線:
005C5E5A 3A1408 cmp dl,[eax+ecx]
下一行是條件跳轉,之后的3行僅執行一次或從不執行(如果條件為true),最后執行i的遞增,循環變量的遞減(將它與零進行比較,然后與其他任何內容進行比較都比較容易) )
因此,我們幾乎無法做帶有優化器的Delphi編譯器無法做到的事情!
如果程序允許,您可以嘗試從最后一個元素到第一個元素反轉搜索方向:
For I := High( List ) downto Low( List ) do
這樣,編譯器將很樂意將i與零進行比較以表明我們已檢查了所有內容(此操作是免費的:當我們將i減一並得到零時,CPU零標志打開!)
但是在這種實現方式中,行為可能有所不同:如果您有多個條目= Value,那么您將獲得的不是第一個,而是最后一個!
另一個非常容易的事情是將這個IndexOf函數聲明為內聯:這樣,您可能在這里沒有函數調用:此代碼將插入到您調用它的每個位置。 函數調用是相當緩慢的事情。
Knuth中還描述了一些瘋狂的方法,該方法如何盡可能快地在簡單數組中進行搜索,他引入了“虛擬”數組的最后一個元素,它等於您的“值”,這樣您就不必檢查邊界了(它總是會在超出范圍之前找到一些東西),因此循環內只有1個條件而不是2個條件。另一種方法是“展開”循環:您在循環內記下2或3個或更多的迭代,因此每個循環的跳躍較少檢查,但這還有更多缺點:它僅對相當大的數組有用,而對於有1個或2個元素的數組可能會更慢。
正如其他人所說:最大的改進是了解您存儲的數據類型:它是經常更改還是長時間保持不變,您是否在尋找隨機元素或某些“領導者”引起了最多的關注。 這些元素是否必須與您放置的順序相同,還是可以根據需要重新排列? 然后,您可以相應地選擇數據結構。 如果您一直在尋找1或2個相同的條目並且可以重新排列它們,那么簡單的“移至最前”方法將非常有用:您不只是返回索引,而是將元素首先移至第一位,所以它下次可以很快找到。
如果數組很長 ,則可以使用內置的x86字符串掃描REP SCAS
。
它使用微碼進行編碼,啟動時間適中,但是在CPU中進行了充分的優化,並且在具有足夠長的數據結構(> = 100字節)的情況下可以快速運行。
實際上,在現代CPU上,它通常勝過非常聰明的RISC代碼。
如果您的數組很短,那么對該例程的優化就無濟於事,因為這樣您的問題就出在問題中未顯示的代碼中,因此我無法為您提供任何答案。
請參閱: http : //docwiki.embarcadero.com/RADStudio/Tokyo/en/Internal_Data_Formats_(Delphi)
function IndexOf({$ifndef RunInSeperateThread} const {$endif} List: TArray<byte>; const Value: byte): integer;
//Lock the array if you run this in a separate thread.
{$ifdef CPUX64}
asm
//RCX = List
//DL = byte.
mov r8,[rcx-8] //3 - get the length ASAP.
push rdi //0 - hidden in mov r,m
mov eax,edx //0 - rename
mov rdi,rcx //0 - rename
mov rcx,r8 //0 - rename
mov rdx,r8 //0 - remember the length
//8 cycles setup
repne scasb //2n - repeat until byte found.
pop rdi //1
neg rcx //0
lea rax,[rdx+rcx] //1 result = length - bytes left.
end;
{$ENDIF}
{$ifdef CPUX86}
asm
//EAX = List
//DL = byte.
push edi
mov edi,eax
mov ecx,[eax-4] //get the length
mov eax,edx
mov edx,ecx //remember the length
repne scasb //repeat until byte found.
pop edi
neg ecx
lea eax,[edx+ecx] //result = length - bytes left.
end;
時機
在我的筆記本電腦上,使用目標字節為1KB的數組,最后給出了以下計時(使用100.0000次運行的最低時間)
Code | CPU cycles
| Len=1024 | Len=16
-------------------------------+----------+---------
Your code optimizations off | 5775 | 146
Your code optimizations on | 4540 | 93
X86 my code | 2726 | 60
X64 my code | 2733 | 69
提速還可以(不錯),但幾乎不值得付出努力。
如果您的數組較短,那么此代碼將無濟於事,您將不得不采用更好的其他方法來優化代碼。
使用二進制搜索時可以加快速度
二進制搜索是O(log n)操作,而O(n)是純搜索。
使用相同的數組,您將在log2(1024)*每次搜索的CPU周期= 10 * 20 +/- 200周期中找到您的數據。 優化代碼的速度提高了10倍以上。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.