Einzelnen Beitrag anzeigen

alzaimar
(Moderator)

Registriert seit: 6. Mai 2005
Ort: Berlin
4.956 Beiträge
 
Delphi 2007 Enterprise
 
#42

Re: Schnellster Stringmatching-Algorithmus in ASM übersetzen

  Alt 8. Dez 2007, 20:08
Hi Amateurprofi. Natürlich ist meine Testumgebung sicher. Ich erzeuge einen Zufallsstring mit dem Alphabet X. Der Suchstring enthält Zeichen, die nicht in X sind.

Ich finde 7% auch sehr gut und daher wird deine Version -mit deiner Erlaubnis- in meine Unit übernommen.

Hier der FastPos-Code für Alle, bei denen der Download nicht klappt.

Delphi-Quellcode:
(******************************************************************************
* FastPos-Unit. Zusammenarbeit der Delphi-Praxis, FastCode-Challenge und    *
*  dem QuickSearch-Algorithmus von Daniel Sunday                            *
*                                                                            *
* Stellt eine Funktion zum Suchen eines Teilstrings in einem String zur      *
* Verfügung. Dabei wird, in Abhängigkeit der Stringlängen der jeweils beste  *
* Algorithmus ausgewählt                                                    *
* Wenn der Suchstring aus einem Zeichen besteht, wird der Gewinner der      *
* FastCode-Challenge 'CharPos' ([url]www.fastcode.org[/url]) aufgerufen                *
*                                                                            *
* Wenn der Suchstring aus 2 oder 3 Zeichen besteht, oder der zu durchsuchende*
* Text relativ kurz ist, dann wird der Gewinner der FastCode-Challenge 'Pos' *
* aufgerufen.                                                                *
* In allen anderen Fällen wird ein modifizierter QuickSearch-Algorithmus    *
* verwendet, der mit einer Optimierung von Timo Raita arbeitet.              *
*                                                                            *
******************************************************************************)

Unit FastPosUnit;

Interface
Function DPPos(Const sSubStr, sString: String): Integer;
Implementation

Uses
  SysUtils;

Function QPosEx(SearchFor, SearchIn: String; Start: integer): integer;
Asm
               pushad // Alle Register auf Stack legen
          sub esp,$400 // Platz für Skiptabelle schaffen
               // Auf dem Stack liegen folgende Daten
               // ESP : Skiptabelle
               // ESP+$400..$410 : EDI, ESI, EBP, ESP, EBX
               // ESP+$414 : EDX Adresse SearchIn
               // ESP+$418 : ECX Start
               // ESP+$41C : EAX Adresse SearchFor
               // ESP+$420 : EBP
          // ESP+$424 : Returnadresse

               // Prüfen, ob SearchFor oder SearchIn leer ist, oder Start negativ ist
               test eax,eax
               je @Fail // SearchFor ist leer
               test edx,edx
               je @Fail // Searchin ist leer
               mov ebx,[eax-4] // Länge SearchFor
               test ebx,ebx
               je @Fail // SearchFor ist leer
               test ecx,ecx
               js @Fail // Start ist negative

               // Erste und letzte mögliche Fundstelle in ESI bzw. EBP
               je @1
               sub ecx,1 // =Start 0-basierend
@1: lea esi,[edx+ecx] // Ab hier soll gesucht werden
               add edx,[edx-4]
               mov ebp,edx
               sub ebp,ebx // Bis hier soll gesucht werden

               // Prüfen, ob Erste mögliche Fundstelle hinter letzter liegt
               cmp esi,ebp
               ja @Fail

               // Prüfen, ob SearchFor nur 1 Zeichen ist
               // Dann ist keine SkipTabelle erforderlich
          cmp ebx,1
               je @Search1


               // Skiptabelle erstellen
               // 1) for i:=0 to 255 do Skip[i]:=n+1
               mov edi,esp
               mov ecx,$100
               mov eax,ebx
               add eax,1 // Length(Searchfor)+1
               rep stosd
               // 2) For i:=0 To n-1 Do Skip[SearchFor[i]]:=n-i
               mov edi,[esp+$41C] // Adresse SearchFor
               mov eax,ebx // Length(Searchfor)
@FillSkip: movzx edx,[edi+ecx] // SearchFor[i]
               mov [esp+edx*4],eax // Skip[SearchFor[i]]:=n-i
               add ecx,1
               sub eax,1
               jne @FillSkip

               // Erstes und Letztes Zeichen von SearchFor in AL/AH
               mov al,[edi] // Erstes Zeichen SearchFor
               mov ah,[edi+ebx-1] // Letzes Zeichen SearchFor

               // Prüfen, ob Length(SearchFor) > 6 ist
               cmp ebx,6
               ja @SearchX
          jmp @Vector.Pointer[ebx*4-8]
@Vector: dd @Search2
               dd @Search3
               dd @Search4
               dd @Search4 // Länge=5
               dd @Search6

// Länge Searchfor > 6
@SearchX: add edi,1 // Auf zweites Zeichen von SearchFor
               mov [esp+$41C],edi // Merken für CompareMem
               jmp @Search
// Länge SearchFor > 6
@SkipX: mov esi,edx // Aufruf ex CompareMem
@Skip: movzx edx,[esi+ebx]
          add esi,[esp+edx*4]
               cmp esi,ebp
               ja @Fail
@Search: cmp [esi],al
               jne @Skip
               cmp [esi+ebx-1],ah
               jne @Skip
               // Zweites bis vorletzten Zeichen von SearchFor prüfen
               mov edx,esi // ESI retten
               mov edi,[esp+$41C] // Zeiger zweites Zeichen von SearchFor
               add esi,1 // Zeiger Text auf nächstes Zeichen
               mov ecx,ebx // Length(SearchFor)
               sub ecx,2 // -2 (Erstes und letztes Zeichen)
          shr ecx,2 // =Anzahl DWords
               repe cmpsd // DWords vergleichen
               jne @SkipX // Nicht gleich
               mov ecx,ebx // Length(SearchFor)
               sub ecx,2 // -2 (Erstes und letztes Zeichen)
               and ecx,3 // =Anzahl restliche Bytes
               repe cmpsb // Bytes vergleichen
               jne @SkipX // Nicht gleich
               mov esi,edx // Fundstelle
               jmp @Found

// Länge SearchFor=1
@Search1: mov ecx,ebp // Letzte mögliche Fundstelle
               sub ecx,esi // - Erste mögliche Fundstelle
          add ecx,1 // = Anzahl zu prüfende Zeichen
               neg ecx
               sub esi,ecx
               mov al,[eax] // zu suchendes Zeichen
@Loop: cmp al,[esi+ecx]
               je @Found1
               add ecx,1
               jne @Loop
               jmp @Fail
@Found1: lea esi,[esi+ecx] // ESI auf Fundstelle
               jmp @Found

// Länge SearchFor=2
@Skip2: movzx edx,[esi+ebx]
               add esi,[esp+edx*4]
               cmp esi,ebp
               ja @Fail
@Search2: cmp [esi],al
               jne @Skip2
               cmp [esi+ebx-1],ah
               jne @Skip2
               jmp @Found

// Länge SearchFor=3
@Skip3: movzx edx,[esi+ebx]
               add esi,[esp+edx*4]
               cmp esi,ebp
          ja @Fail
@Search3: cmp [esi],al
               jne @Skip3
               cmp [esi+ebx-1],ah
               jne @Skip3
               mov dx,[edi]
               cmp dx,[esi]
               jne @Skip3
               jmp @Found

// Länge SearchFor=4 oder 5
@Skip4: movzx edx,[esi+ebx]
               add esi,[esp+edx*4]
          cmp esi,ebp
               ja @Fail
@Search4: cmp [esi],al
               jne @Skip4
               cmp [esi+ebx-1],ah
               jne @Skip4
               mov edx,[edi]
               cmp edx,[esi]
               jne @Skip4
               jmp @Found

// Länge SearchFor=6
@Skip6: movzx edx,[esi+ebx]
               add esi,[esp+edx*4]
          cmp esi,ebp
               ja @Fail
@Search6: cmp [esi],al
               jne @Skip6
               cmp [esi+ebx-1],ah
               jne @Skip6
               mov edx,[edi+1]
               cmp edx,[esi+1]
               jne @Skip6
               jmp @Found

@Found: // Gefunden, Index berechnen
               sub esi,[esp+$414]
          add esi,1
               jmp @SetRes

@Fail: // Nicht gefunden, Result=0
               xor esi,esi

@SetRes: // Result speichern
               mov [esp+$41C],esi

               // Skiptabelle vom Stack nehmen
               add esp,$400

               // Register wieder herstellen, Result in EAX
               popad
End;

Function CharPos_JOH_SSE2_1_b(Ch: Char; Const Str: AnsiString): Integer;
Asm
  test edx, edx
  jz @@NullString
  mov ecx, [edx-4]
  push ebx
  mov ebx, eax
  cmp ecx, 16
  jl @@Small
@@NotSmall:
  mov ah, al {Fill each Byte of XMM1 with AL}
  movd xmm1, eax
  pshuflw xmm1, xmm1, 0
  pshufd xmm1, xmm1, 0
@@First16:
  movups xmm0, [edx] {Unaligned}
  pcmpeqb xmm0, xmm1 {Compare First 16 Characters}
  pmovmskb eax, xmm0
  test eax, eax
  jnz @@FoundStart {Exit on any Match}
  cmp ecx, 32
  jl @@Medium {If Length(Str) < 32, Check Remainder}
@@Align:
  sub ecx, 16 {Align Block Reads}
  push ecx
  mov eax, edx
  neg eax
  and eax, 15
  add edx, ecx
  neg ecx
  add ecx, eax
@@Loop:
  movaps xmm0, [edx+ecx] {Aligned}
  pcmpeqb xmm0, xmm1 {Compare Next 16 Characters}
  pmovmskb eax, xmm0
  test eax, eax
  jnz @@Found {Exit on any Match}
  add ecx, 16
  jle @@Loop
  pop eax {Check Remaining Characters}
  add edx, 16
  add eax, ecx {Count from Last Loop End Position}
  jmp dword ptr [@@JumpTable2-ecx*4]
  nop
  nop
@@NullString:
  xor eax, eax {Result = 0}
  ret
  nop
@@FoundStart:
  bsf eax, eax {Get Set Bit}
  pop ebx
  inc eax {Set Result}
  ret
  nop
  nop
@@Found:
  pop edx
  bsf eax, eax {Get Set Bit}
  add edx, ecx
  pop ebx
  lea eax, [eax+edx+1] {Set Result}
  ret
@@Medium:
  add edx, ecx {End of String}
  mov eax, 16 {Count from 16}
  jmp dword ptr [@@JumpTable1-64-ecx*4]
  nop
  nop
@@Small:
  add edx, ecx {End of String}
  xor eax, eax {Count from 0}
  jmp dword ptr [@@JumpTable1-ecx*4]
  nop
@@JumpTable1:
  dd @@NotFound, @@01, @@02, @@03, @@04, @@05, @@06, @@07
  dd @@08, @@09, @@10, @@11, @@12, @@13, @@14, @@15, @@16
@@JumpTable2:
  dd @@16, @@15, @@14, @@13, @@12, @@11, @@10, @@09, @@08
  dd @@07, @@06, @@05, @@04, @@03, @@02, @@01, @@NotFound
@@16:
  add eax, 1
  cmp bl, [edx-16]
  je @@Done
@@15:
  add eax, 1
  cmp bl, [edx-15]
  je @@Done
@@14:
  add eax, 1
  cmp bl, [edx-14]
  je @@Done
@@13:
  add eax, 1
  cmp bl, [edx-13]
  je @@Done
@@12:
  add eax, 1
  cmp bl, [edx-12]
  je @@Done
@@11:
  add eax, 1
  cmp bl, [edx-11]
  je @@Done
@@10:
  add eax, 1
  cmp bl, [edx-10]
  je @@Done
@@09:
  add eax, 1
  cmp bl, [edx-9]
  je @@Done
@@08:
  add eax, 1
  cmp bl, [edx-8]
  je @@Done
@@07:
  add eax, 1
  cmp bl, [edx-7]
  je @@Done
@@06:
  add eax, 1
  cmp bl, [edx-6]
  je @@Done
@@05:
  add eax, 1
  cmp bl, [edx-5]
  je @@Done
@@04:
  add eax, 1
  cmp bl, [edx-4]
  je @@Done
@@03:
  add eax, 1
  cmp bl, [edx-3]
  je @@Done
@@02:
  add eax, 1
  cmp bl, [edx-2]
  je @@Done
@@01:
  add eax, 1
  cmp bl, [edx-1]
  je @@Done
@@NotFound:
  xor eax, eax
  pop ebx
  ret
@@Done:
  pop ebx
End;

Procedure Filler2;
Asm
 nop
End;

Function Pos_JOH_IA32_6(Const SubStr: AnsiString; Const Str: AnsiString): Integer;
Asm {Slightly Cut-Down version of PosEx_JOH_6}
  push ebx
  cmp eax, 1
  sbb ebx, ebx {-1 if SubStr = '' else 0}
  sub edx, 1 {-1 if S = ''}
  sbb ebx, 0 {Negative if S = '' or SubStr = '' else 0}
  jl @@InvalidInput
  push edi
  push esi
  push ebp
  push edx
  mov edi, [eax-4] {Length(SubStr)}
  mov esi, [edx-3] {Length(S)}
  cmp edi, esi
  jg @@NotFound {Offset to High for a Match}
  test edi, edi
  jz @@NotFound {Length(SubStr = 0)}
  lea ebp, [eax+edi] {Last Character Position in SubStr + 1}
  add esi, edx {Last Character Position in S}
  movzx eax, [ebp-1] {Last Character of SubStr}
  add edx, edi {Search Start Position in S for Last Character}
  mov ah, al
  neg edi {-Length(SubStr)}
  mov ecx, eax
  shl eax, 16
  or ecx, eax {All 4 Bytes = Last Character of SubStr}
@@MainLoop:
  add edx, 4
  cmp edx, esi
  ja @@Remainder {1 to 4 Positions Remaining}
  mov eax, [edx-4] {Check Next 4 Bytes of S}
  xor eax, ecx {Zero Byte at each Matching Position}
  lea ebx, [eax-$01010101]
  not eax
  and eax, ebx
  and eax, $80808080 {Set Byte to $80 at each Match Position else $00}
  jz @@MainLoop {Loop Until any Match on Last Character Found}
  bsf eax, eax {Find First Match Bit}
  shr eax, 3 {Byte Offset of First Match (0..3)}
  lea edx, [eax+edx-3] {Address of First Match on Last Character + 1}
@@Compare:
  cmp edi, -4
  jle @@Large {Lenght(SubStr) >= 4}
  cmp edi, -1
  je @@SetResult {Exit with Match if Lenght(SubStr) = 1}
  movzx eax, word ptr [ebp+edi] {Last Char Matches - Compare First 2 Chars}
  cmp ax, [edx+edi]
  jne @@MainLoop {No Match on First 2 Characters}
@@SetResult: {Full Match}
  lea eax, [edx+edi] {Calculate and Return Result}
  pop edx
  pop ebp
  pop esi
  pop edi
  pop ebx
  sub eax, edx {Subtract Start Position}
  ret
@@NotFound:
  pop edx {Dump Start Position}
  pop ebp
  pop esi
  pop edi
@@InvalidInput:
  pop ebx
  xor eax, eax {No Match Found - Return 0}
  ret
@@Remainder: {Check Last 1 to 4 Characters}
  mov eax, [esi-3] {Last 4 Characters of S - May include Length Bytes}
  xor eax, ecx {Zero Byte at each Matching Position}
  lea ebx, [eax-$01010101]
  not eax
  and eax, ebx
  and eax, $80808080 {Set Byte to $80 at each Match Position else $00}
  jz @@NotFound {No Match Possible}
  lea eax, [edx-4] {Check Valid Match Positions}
  cmp cl, [eax]
  lea edx, [eax+1]
  je @@Compare
  cmp edx, esi
  ja @@NotFound
  lea edx, [eax+2]
  cmp cl, [eax+1]
  je @@Compare
  cmp edx, esi
  ja @@NotFound
  lea edx, [eax+3]
  cmp cl, [eax+2]
  je @@Compare
  cmp edx, esi
  ja @@NotFound
  lea edx, [eax+4]
  jmp @@Compare
@@Large:
  mov eax, [ebp-4] {Compare Last 4 Characters of S and SubStr}
  cmp eax, [edx-4]
  jne @@MainLoop {No Match on Last 4 Characters}
  mov ebx, edi
@@CompareLoop: {Compare Remaining Characters}
  add ebx, 4 {Compare 4 Characters per Loop}
  jge @@SetResult {All Characters Matched}
  mov eax, [ebp+ebx-4]
  cmp eax, [edx+ebx-4]
  je @@CompareLoop {Match on Next 4 Characters}
  jmp @@MainLoop {No Match}
End;

Function DPPos(Const sSubStr, sString: String): Integer;
Var
  i: Integer;
  c: Char;
Begin
  Case Length(sSubStr) Of
    0:
      Result := 0;
    1:
      Result := CharPos_JOH_SSE2_1_b(sSubStr[1], sString);
    2, 3:
      Result := Pos_JOH_IA32_6(sSubStr, sString);
  Else
    If Length(sString) < 1000 Then // 1000 is ne Hausnummer
      Result := Pos_JOH_IA32_6(sSubStr, sString)
    Else
      Result := QPosEx(sSubStr, sString,1); // Dann kann man auf das '(n<3) or' verzichten!
  End
End;
End.
"Wenn ist das Nunstruck git und Slotermeyer? Ja! Beiherhund das Oder die Flipperwaldt gersput!"
(Monty Python "Joke Warefare")
  Mit Zitat antworten Zitat