簡體   English   中英

Delphi優化IndexOf函數

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM