Autor Beitrag
Backslash
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 202

WIN XP
Delphi 5 Ent, Delphi 2005 Prof
BeitragVerfasst: Mo 01.08.05 20:09 
Hallo Freunde :D

ich habe vor längerem eine geniale Seite im Internet gefunden in der man eine umfangreiche Beschreibung für die Beschleunigung von Sortieralgorithmen findet. Die URL der Seite ist [url]www.linux-related.de[/url]

Ich habe bisher den Quicksort-Algorithmus zum Sortieren von Datensätzen benutzt. Doch mittlerweile benutze ich den neuen "Linear Straight Radix Sort - Algorithmus" von dieser Seite.

Der Originalcode des Algorithmus ist:

ausblenden Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
inline unsigned getbits(unsigned x, int k, int j){ return (x>>k)&~(~0<<j); }

void linoptstraightradix(int a[], int N, int bits){
    int M=1, m, i, j, pass, *count, b[N];
    m=bits/4;
    for(i=0; i<m; i++,M*=2);          //2^m berechnen
    count=(int*)malloc(M*sizeof(int));
    for(pass=0; pass<bits/m; pass++){        //die Bits der Reihe nach betrachten
        for(j=0; j<M; j++) count[j]=0;        //count[] mit 0 initialisieren
        for(i=0; i<N; i++) count[getbits(a[i],pass*m,m)]++;  //Vorkommen der Schlüssel speichern
        for(j=1; j<M; j++)
            count[j]=count[j-1]+count[j];      //Positionen im Array bestimmen
        for(i=N-1; i>=0; i--)
            b[--count[getbits(a[i],pass*m,m)]]=a[i];    //in b einordnen, jeweiligen Zähler verringern
        for(i=0; i<N; i++) a[i]=b[i];        //auf a zurück speichern
    }
    free(count);
}


Wie hier zu sehen, handelt es sich dabei um C - Code und nicht Delphicode. Ich hab mir mal den Spaß erlaubt und den Algorithmus übersetzt, da ich im Internet keine Delphi-Implementation fand.

Hier der erste Versuch:

ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
type
  TDWORDArray = Array of DWORD;

// holt bit k (von rechts) bis bit j aus x raus und gibt es als Rückgabewert
function getbits(x : DWORD; k : Integer; j : Integer) : DWORD;
begin
  Result := x shr k;
  Result := Result AND ($FFFFFFFF shr (32 - j));
end;

// optimiertes "linear optimized straight radix sort"
procedure linoptstraightradix(var a : TDWORDArray; N : Integer; bits : Integer);
var
  count : TDWORDArray;
  b     : TDWORDArray;
  M     : Integer;
  m1    : Integer;
  i     : Integer;
  j     : Integer;
  pass  : Integer;
  temp  : DWORD;
  PPtr  : Pointer;
begin
  M := 1;
  SetLength(b, N);
  m1 := bits div 4;
  for i := 0 to m1 - 1 do
    M := M * 2;
  SetLength(count, M);
  for pass := 0 to bits div m1 - 1 do
  begin
    for j := 0 to M - 1 do
      count[j] := 0;
    for i := 0 to N - 1 do
      inc(count[getbits(a[i], pass * m1, m1)], 1);
    for j := 1 to M - 1 do
      count[j] := count[j-1] + count[j];
    for i := N - 1 downto 0 do
    begin
      temp := getbits(a[i], pass * m1, m1);
      count[temp]    := count[temp] - 1;
      b[count[temp]] := a[i];
    end;
    for i := 0 to N - 1 do
      a[i] := b[i];
  end;
end;


Tests haben gezeigt, dass der Algorithmus (so wie er hier vorliegt) nicht den Geschwindigkeitsvorteil von 30-40% bringt, wie ihn der Autor auf linux-related.de versprochen hat. Ich bin also hergegangen und hab die Getbits-Routine nicht aufgerufen sondern direkt in die Sortierfunktion eingebunden und am Ende weise ich dem Array a die Adresse vom Array b zu und dem Array b die Adresse des Arrays a, da beide Arrays gleich groß und vom gleichen Typ sind. So erspare ich mir ein komplettes Array umzusortieren.

Die optimierte Fassung sieht so aus:

ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
// optimiertes "linear optimized straight radix sort"
procedure linoptstraightradix(var a : TDWORDArray; N : Integer; bits : Integer);
var
  count : TDWORDArray;
  b     : TDWORDArray;
  M     : Integer;
  m1    : Integer;
  i     : Integer;
  j     : Integer;
  pass  : Integer;
  temp  : DWORD;
  PPtr  : Pointer;
begin
  M := 1;
  SetLength(b, N);
  m1 := bits div 4;
  for i := 0 to m1 - 1 do
    M := M * 2;
  SetLength(count, M);
  for pass := 0 to bits div m1 - 1 do
  begin
    for j := 0 to M - 1 do
      count[j] := 0;
    for i := 0 to N - 1 do
    // direkte Zuweisung ohne Aufruf von getbits (viel schneller)
    inc(count[(a[i] shr (pass * m1)) AND ($FFFFFFFF shr (32 - m1))], 1);
    for j := 1 to M - 1 do
      count[j] := count[j-1] + count[j];
    for i := N - 1 downto 0 do
    begin
      // direkte Zuweisung ohne Aufruf von getbits (viel schneller)
      temp := (a[i] shr (pass * m1)) AND ($FFFFFFFF shr (32 - m1));
      count[temp]    := count[temp] - 1;
      b[count[temp]] := a[i];
    end;
    // Arraypointer vertauschen, um Zeit zu sparen, anstatt jeden
    // einzelnen Wert von Array b nach Array a zu kopieren
    PPtr := Pointer(a);
    DWORD(Pointer(a)) := DWORD(Pointer(b));
    DWORD(Pointer(b)) := DWORD(PPtr);
  end;
end;


Und tatsächlich, so ist der Algorithmus 30-40% schneller als Quicksort. Ich hab mit Arrays von ca. 200000 bis 2 mio. DWORD - Elementen gearbeitet. Einziger Nachteil des Algorithmus ist, dass er doppelt soviel Arbeitsspeicher braucht wie das zu sortierende Array groß ist. Doch bei ein paar hunderttausend Elementen spielt das auf meinem PC mit 512 MB Ram keine Rolle.

Achja: Ich denke die meisten von euch wissen schon was noch beachtet werden muss. Aber ich sags nochmal, falls das jemand nicht weiß. Ich hab mit einem dynamischen Array von DWORD-Elementen gearbeitet. So wird das Programmstack nicht überlastet und es gibt keinen Stack-Overflow. Bevor ihr ein Array mit Daten befüllt, die ihr sortieren möchtet, müsst ihr mit Setlength(Array, Laenge) die Länge bestimmen und diese auch dem Sortieralgorithmus übergeben.

Mich würde interessieren ob jemand vielleicht noch ein paar Tipps hat, wie man den Algo unter Umständen weiter optimieren kann. Falls keine Möglichkeiten mehr existieren (abgesehen vielleicht von Inline-Assembler), so habt ihr halt einen schnellen Sortieralgorithmus der viel schneller als Quicksort sortiert. 8)

Ich danke an dieser Stelle den Autoren von linux-related.de für das umfangreiche Tutorial.

Gruß

Backslash
SMO
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 120
Erhaltene Danke: 18


D2005 Personal
BeitragVerfasst: Mo 01.08.05 20:53 
Sehr interessant, danke fürs Posten! Wenn ich demnächst mehr Zeit habe, schaue ich mir den Algorithmus und diese Webseite mal genauer an.

Zitat:
Ich bin also hergegangen und hab die Getbits-Routine nicht aufgerufen sondern direkt in die Sortierfunktion eingebunden

Das war richtig, denn im C-Code ist Getbits als "inline" deklariert. Es ist dort also auch keine Routine, die normal aufgerufen wird, sondern der Code wird direkt an die Stelle des Aufrufs eingefügt. Delphi sollte das ab Version 2005 übrigens auch können:
ausblenden Delphi-Quelltext
1:
function getbits(x : DWORD; k : Integer; j : Integer) : DWORD; inline;					

Wobei das für den Delphi-Compiler nur ein Hinweis ist, dem er nicht immer folgen muss oder kann. Insofern ist das direkte Einbinden in die Sortierfunktion, so wie du es gemacht hast, wohl vorzuziehen, und es verschlechtert ja auch nicht nennenswert die Lesbarkeit des Codes.
AXMD
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 4006
Erhaltene Danke: 7

Windows 10 64 bit
C# (Visual Studio 2019 Express)
BeitragVerfasst: Mo 01.08.05 21:18 
Äußerst interessant. Ben könnte da vielleicht noch was mit Assembler rausholen, hm ;)

AXMD
Backslash Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 202

WIN XP
Delphi 5 Ent, Delphi 2005 Prof
BeitragVerfasst: Mo 01.08.05 22:05 
Danke SMO für den Tipp mit der Inline-Funktion. Das zum Beispiel hab ich auch übersehen :oops: . Jetzt weiß ich warum der Code schneller ist, wenn ich getbits direkt einbinde. Ich hab versucht die Funktion getbits über Assembler zu implementieren. Das funktioniert zwar, ist abar nicht schneller (weil ich sie nicht als inline deklariert hatte).

Ich muss aber zugeben, dass ich nur ein paar Assembler-Grundkenntnisse hab. Deswegen möge man mir die Wahl der Register vergeben. Das wäre natürlich genial, wenn hier jemand eine Assembler-optimierte Routine angeben könnte.

Hier erstmal der Quellcode meines Versuchs, getbits über inline-assembler zu realisieren:

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
function getbits(x : DWORD; k : Integer; j : Integer) : DWORD;
begin
// Assemblercode von getbits (bringt aber keine Geschwindigkeitsverbesserung, hab mit gettickcount die Zeit gestoppt)
  asm
    mov eax, x
    mov ecx, k
    mov edx, j
    shr eax, cl  // Result := x shr k;

    or  edi, $FFFFFFFF
    mov cl,  32
    sub cl,  dl
    shr edi, cl  // ($FFFFFFFF shr (32 - j))

    and eax, edi // Result := Result AND ...

    mov Result, eax
  end;
end;


Ich möchte noch anfügen, dass der Algorithmus laut Angaben des Autors bei linux-related.de nur dann 30-40% schneller wie quicksort ist, wenn die Anordnung der Datensätze ziemlich zufällig ist.

Auf der Site gibt es übrigens auch einen Hybrid aus Quicksort und MedianSort (wenn ich mich recht erinnere), der Quicksort um ca. 6% schneller macht. Auch diesen Code hab ich mal von c nach delphi übersetzt. Falls Interesse besteht, poste ich ihn gern hier. In delphi hab ich per Zeitstoppung tatsächlich 5-6% Geschwindigkeitsvorteil bekommen.
delfiphan
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2684
Erhaltene Danke: 32



BeitragVerfasst: Di 02.08.05 01:22 
Der Algorithmus ist auch bekannt unter dem Namen Bucketsort ([url=de.wikipedia.org/wik...rt]Bucketsort[/url],Suche in: Delphi-Forum, Delphi-Library BUCKETSORT,Suche bei Google BUCKETSORT). Der Algorithmus ist nicht etwa neu oder so. Er wird vor allem in 3D Anwendungen benützt. Der Nachteil ist, dass der Wertebereich bekannt und auch diskret sein muss.
Übrigens: Statt Buckets für die einzelnen Bits zu machen wäre es viel schneller, 4 oder 8 Bits pro Durchlauf zu bearbeiten. Man muss dann halt 16 oder 256 Buckets pro Durchlauf bereitstellen; jedoch würd ich meinen, dass das für grössere Datensätze wesentlich schneller geht; vor allem, da man dann die Bits nicht aus den Bytes extrahieren muss.
Wenn man z.B. nur Zahlen von 0..255 hat, ist klar, dass es Sinn macht 8-Bit Radix Sortierung durchzuführen. Es ist dann genau 1 Durchlauf und kein "Splitting" nötig, um das ganze zu sortieren: Man zählt einfach welcher Buchstabe wie häufig vorkommt und schon ist das ganze "sortiert".
Backslash Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 202

WIN XP
Delphi 5 Ent, Delphi 2005 Prof
BeitragVerfasst: Di 02.08.05 16:22 
hallo delfiphan. Danke für den Tipp mit dem Namen "Bucketsort". Da hast du vollkommen recht. Der ist tatsächlich nicht sehr neu. Ich hab irgendwo gelesen, dass es den glaub ich seid 1996 geben soll, bin mir aber nicht sicher. :oops:

Die Idee mit den 4 oder 8 Bits pro Durchlauf klingt interessant. Ich werde mal schauen, ob ich den Algorithmus so umbauen kann, dass er das direkt auf einmal macht. Im großen und ganzen hab ich mich bisher an die Vorgaben der Autoren gehalten. Ich denke, ich werde deine Idee mal ausprobieren. Vielleicht bringt es tatsächlich einen Vorteil :wink:


Also danke für die Tipps. Gruß Backslash.

Nachtrag: Ich hab mir deine Links zum Thema Bucketsort angesehen und kann immer noch keine andere Delphi-Implementierung finden, auch nicht bei google. Ich schätze, die Arbeit den Code zu übersetzen war also nicht umsonst :wink:

Dennoch hab ich bei google eine assembler-implementierung gefunden, die angeblich um den Faktor 10 schneller als quicksort (standard c++- bibliothek) sein soll. Ich werd mal schauen, ob ich die als inline-assemblerroutine in delphi einbinden kann. wenn es mir gelingt, poste ich hier den code


Zuletzt bearbeitet von Backslash am Di 02.08.05 16:40, insgesamt 1-mal bearbeitet
Gausi
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 8548
Erhaltene Danke: 477

Windows 7, Windows 10
D7 PE, Delphi XE3 Prof, Delphi 10.3 CE
BeitragVerfasst: Di 02.08.05 16:39 
Da hier alle von diesem Algorithmus so begeistert sind, will ich mal n paar Worte sagen, weswegen der in aller Regel nicht als Standard verwendet wird.

1. Der Speicherbedarf. Wurde schon angesprochen. "Normale" Sortierverfahren wie Bubblesort, Heapsort, Quicksort sind In-Situ-Verfahren. Sie benötigen zum Sortieren keinen zusätzlichen Speicher (außer ein paar Byte für die Swap-Variable). Der hier vorgestellte Algorithmus benötigt doppelt soviel Speicher.

2. Laufzeit. Der Algorithmus wird langsam, wenn die zu sortierenden Zwahlen sehr groß werden, da für jedes Bit/Byte/Buchstabe eine "Runde" durchgeführt wird. zur Verdeutlichung ein Beispiel:(*)
Quicksort benötigt im Mittel log(N)*N viele Vergleiche, um eine Folge mit N vielen Einträgen zu sortieren. Das sind bei 1024 Werten ungefahr 10.000 Vergleiche.
Dieser Algo hier braucht ca. (Anzahl der Stellen)*N viele Vergleiche. Wenn ich nun z.B. Wörter sortieren will, die jeweils 20 Zeichen haben, brauche ich bei einer Wortliste von 1024 Wörtern schon 20.000 Vergleiche - grob doppelt soviele wie Quicksort.

Das Verfahren ist schnell, gar keine Frage. Aber nur unter gewissen Voraussetzungen und mit einem recht hohen Speicherbedarf. Daher wird meist Quicksort genommen, da sich dieser in der Praxis sehr bewährt hat.

(*)@die theoretischen Informatiker hier: Mir ist klar, dass man so eigentlich nicht rechnen kann. Aber das Problem wird hoffentlich trotzdem klar.

_________________
We are, we were and will not be.
Backslash Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 202

WIN XP
Delphi 5 Ent, Delphi 2005 Prof
BeitragVerfasst: Di 02.08.05 17:01 
Hier erstmal der Nachtrag. Ich hab doch eine einfache Delphi-Implementierung im Internet gefunden. Das findet ihr in diesem schönen Dokument. www.gym1.at/schulinf...vogl/algorithmen.pdf

@Gausi, ich weiß nicht ob du dir die Implementierung von mir bzw. von Linux-Related genau angeschaut hast. Vor jedem Aufruf wird ein optimales M berechnet (hier Bitanzahl div 4). Das heißt wenn ich ein Array mit 32-Bitwerten sortiere, schafft das der Algorithmus in 4 Durchläufen. Wenn ich das richtig verstanden habe, dann würde das der Optimierungsidee von Delfiphan entsprechen.

Natürlich ist bucketsort oder radixsort sicher keine Patentlösung für jedes Problem. Ich kann ihn für meine Zwecke jedoch besser als quicksort einsetzen. Zudem sortiere ich maximal Zahlen von 32 Bits und beim Debuggen hat sich gezeigt, dass das optimale m gleich 8 ist (bei 32 bit Zahlengröße) und demnach nur 4 Durchläufe in der äußersten Schleife benötigt werden. Das Array mit den buckets enthält also 256 Einträge. :)

Anmerkung:
Man darf nicht vergessen, dass quicksort rekursiv arbeitet und bei jedem Aufruf der Funktion meines Wissens nach eine Kopie der übergebenden Variablen im Arbeitsspeicher (bzw. im Programmstack) gespeichert wird. Das bedeutet auch zusätzlichen Speicheraufwand, wenn ich das richtig sehe. Bei Arrays mit mehreren hunderttausend Einträgen musste ich bei mir entweder den Programmstack auf unglaublich große Werte (manuell) einstellen. Bei mehreren Arrays in dieser Größe gabs jedesmal einen Stackoverflow. Deswegen hab ich die Quicksortimplementierung bei mir auf dynamische Arrays umgestellt. :wink:

Alles in allem denke ich dass meine Frage beantwortet ist. Danke für die Hindweise und Ideen. Ich werd nun schauen, ob ich was drauß machen kann.

KLeiner Típp: Wie der name schon sagt, ist die Ausführzeit von der optimierten Variante des Bucketsort annähernd linear. Deine Berechung weiter oben stimmt zwar, trifft aber nur auf die "Basisvariante" von Bucketsort zu, der wirklich hergeht und das Array für die Buckets so groß macht wie 2^bits Möglichkeiten existieren. (Guckst du den Link, den ich in dem Posting drinnen hab. Da drinnen findest du die Basisvariante und siehst den Unterschied)
BenBE
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 8721
Erhaltene Danke: 191

Win95, Win98SE, Win2K, WinXP
D1S, D3S, D4S, D5E, D6E, D7E, D9PE, D10E, D12P, DXEP, L0.9\FPC2.0
BeitragVerfasst: Sa 23.06.07 15:53 
Sorry, dass ich diesen alten Thread noch einmal aufgreife, aber ich hätte da ggf. noch eine Idee (oder besser gesagt, ein Kumpel hat mich auf diese gebracht). Er hat dabei einmal versucht, BucketSort zur Basis 2 zu implementieren und hat dabei seine Variante so umgeschrieben, dass er diese INLINE ausführen konnte (also direkt im zu sortierenden Array).

Den Source hab ich leider grad nicht vorliegen (und auch eine Portierung nach Delphi steht noch aus) ... Falls Interesse bestehen sollte, kann ich den Algo ja mal posten.

_________________
Anyone who is capable of being elected president should on no account be allowed to do the job.
Ich code EdgeMonkey - In dubio pro Setting.
alzaimar
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 2889
Erhaltene Danke: 13

W2000, XP
D6E, BDS2006A, DevExpress
BeitragVerfasst: Sa 23.06.07 17:25 
Der hier vorgestellte Radix Sort ist eine eine Spezialisierung des Bucketsorts und geht nur mit Integer-Zahlen.

www.nist.gov/dads/HTML/radixsort.html

Ist der Wertebereich bekannt, geht es auch mit 'Counting Sort', das gänzlich ohne Vergleiche auskommt, und im Einzelfall noch schneller sein dürfte. Leider ist der Speicherbedarf proportional zum Wertebereich. Wenn also ein Array mit Zahlen zwischen -maxint und maxint zu sortieren ist, benötigt aber ein Array mit 'nur' 2^23 Elementen. :shock:

Bucketsort wird vom MS-SQL (und vermutlich vielen anderen DBMS) intern verwendet, um die Datenmengen bei der Abarbeitung von Queries zu sortieren.

In der Praxis wird aber eigentlich immer Quicksort verwendet, da er kompakt, übersichtlich und ohne extra Speicher auskommt. Das wurde aber bereits gesagt.

Aber es ist gut zu wissen, das es eben auch schnellere Varianten gibt.

@BenBE: Wenn dein Kumpel einen Radix-Sort zur Basis 2 implementiert, dann wäre das Laufzeitverhalten 32*n, also O(n), Quicksort ist O(n log n), also ist Radix-(2)-Sort so gesehen 'besser'.
ABER: Bei 100.000 Zahlen müsste man bei Radix-(2) 32*100.000 Vergleiche, bei Quicksort jedoch nur 13*100.000 Vergleiche durchführen....

Radix-Sort ist also erst bei mehr als 2^32 Elementen im Mittel schneller. Da auch Einiges an Rechenoperationen durchzuführen ist, scheint eine Radix-(2) Implementierung nicht vorteilhaft. Allerdings käme es auf den Versuch an.

_________________
Na denn, dann. Bis dann, denn.
BenBE
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 8721
Erhaltene Danke: 191

Win95, Win98SE, Win2K, WinXP
D1S, D3S, D4S, D5E, D6E, D7E, D9PE, D10E, D12P, DXEP, L0.9\FPC2.0
BeitragVerfasst: Sa 23.06.07 18:10 
Im Wesentlichen bestand der Trick dieser Implementierung darin, dass man die Daten inline über das Carry-Flag beim Rotieren verglichen hat. D.h. wenn man 32 mal einen Int32 rotiert, erhält man diesen wieder.

Der Vorteil dürfte aber im Wesentlichen darin liegen, dass das Vergleichen im Wesentlichen über Datenzugriffe realisiert wird, die eh bereits ausgeführt werden.

Wenn ich die Tage mal Zeit finde, implementiere ich die besagte Variante des Algos ... Wird aber voraussichtlich nicht ohne ASM in Delphi gehen, da ich in-Memory Rotationen ausführen muss, die Delphi nicht direkt zulässt.

Ich meld mich wie gesagt ...

//Edit: Hab jetzt mal kurz ne Grob-Implementierung gemacht:
ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
procedure Sort_BinBucket(const Data: TDWORDArray);
//  EAX ==> Pointer to the array to sort
asm
    MOV     ECX, 32

    //Required to hold 3 pointers ...
    PUSH    ESI
    PUSH    EDI

@@SortLoop:
    //Initialize the pointers ... ESI = Start of Data, EDI = End of Data
    MOV     ESI, EAX
    MOV     EDX, DWORD PTR [EAX - 4]
    LEA     EDI, DWORD PTR [EAX + 4*EDX - 4]

@@SortDataLoop_0:
    //Sort all entries with an 0 at current LSb ...
    ROR     DWORD PTR [ESI], 1  //Copy the LSb into CF
    JC      @@SortDataLoop_0_Detected1    

    //The element is already in the right place ... skip over to the next ...
    ADD     ESI, 4
    DEC     EDX
    JNZ     @@SortDataLoop_0
    JMP     @@SortDataLoop_E

@@SortDataLoop_0_Detected1:
    //Memory Exchange ESI and EDI contents
    XCHG    EAX, DWORD PTR [ESI]
    XCHG    EAX, DWORD PTR [EDI]
    XCHG    EAX, DWORD PTR [ESI]

    //EDI is in the right place ... so skip it ...
    SUB     EDI, 4
    DEC     EDX
    JZ      @@SortDataLoop_E

@@SortDataLoop_1:
    //Sort all entries with an 1 at current LSb ...
    ROR     DWORD PTR [EDI], 1  //Copy the LSb into CF
    JNC     @@SortDataLoop_1_Detected0
    SUB     EDI, 4
    DEC     EDX
    JNZ     @@SortDataLoop_0

@@SortDataLoop_1_Detected0:
    //Memory Exchange ESI and EDI contents
    XCHG    EAX, DWORD PTR [EDI]
    XCHG    EAX, DWORD PTR [ESI]
    XCHG    EAX, DWORD PTR [EDI]

    //ESI is in the right place ... so skip it ...
    ADD     ESI, 4
    DEC     EDX
    JNZ     @@SortDataLoop_0
    
@@SortDataLoop_E:
    DEC     ECX
    JNZ     @@SortLoop

    //Restore them for Delphi to be happy ...
    POP     EDI
    POP     ESI
end;


Hab se noch nicht im Debugger getestet, sollte aber funzen, wenn ich mich nicht vertan hab.

P.S.: Und alles, OHNE irgendwelchen Speicher zu benötigen ;-) (Naja, außer dem Stackframe für die 3 Register, die Delphi gern wieder haben möchte)...

_________________
Anyone who is capable of being elected president should on no account be allowed to do the job.
Ich code EdgeMonkey - In dubio pro Setting.
Chryzler
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 1097
Erhaltene Danke: 2



BeitragVerfasst: So 24.06.07 15:51 
user profile iconBenBE hat folgendes geschrieben:
Er hat dabei einmal versucht, BucketSort zur Basis 2 zu implementieren und hat dabei seine Variante so umgeschrieben, dass er diese INLINE ausführen konnte (also direkt im zu sortierenden Array).

Und was bedeutet das? Zur Basis 2? Ist der Algo jetzt doppelt so schnell oder was? :o Was genau ist jetzt der Unterschied zum "normalen" Bucket-Sort?
Horst_H
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1654
Erhaltene Danke: 244

WIN10,PuppyLinux
FreePascal,Lazarus
BeitragVerfasst: So 24.06.07 16:31 
Hallo,

man kann ja noch anders vorgehen um Cardinals zu sortieren, dummerweise mit dem doppelten Platzbedarf.
In 4 (von LSByte nach MSByte) Durchläufen wird jeweils byteweise sortiert:
a. Zaehlen des Vorkommens jedes Byte's von v(n) 0..255.
b. Indices berechnen (I_=0;I_n+1 := I_n+v(n)
c. Alle Zahlen nochmals abklappern und entsprechend Ihres Bytewertes n bei I_n eintragen und I_n erhöhen.


Damit saust man insgesamt 8- mal durch gesamten Bestand, wobei 8 mal verglichen (wobei der Wert direkt als Index wirkt ) wird 4 mal komplett bewegt wird.
Wenn man statt Byte Word nutzt also (65536 Indizes ~256 kByte nicht sehr Cache freundlich, könnte aber schneller sein ) Nur 4 Durchläufe und 2 komplett bewegt.

Gruß Horst
P.S.:
Sowas hatte ich irgendwo hier schonmal, mal schauen wo.
EDIT
Meine Güte, das beschriebene Verfahren entspricht dem vorgestelltem mit bits= 8 und ist auch genauso schnell.
Recht genau 1 sek für 10 Mio zufällige DwordWerte.(1.7 Ghz Pentium M)


Zuletzt bearbeitet von Horst_H am So 24.06.07 18:17, insgesamt 1-mal bearbeitet
Hajoseb
ontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic starofftopic star
Beiträge: 42



BeitragVerfasst: So 24.06.07 17:51 
Sollte man dafür
ausblenden C#-Quelltext
1:
    for(i=0; i<m; i++,M*=2);          //2^m berechnen					

nicht besser eine fertige Tabelle für alle vorkommenden 2^m nehmen, um nicht jedesmal den Wert neu zu berechnen ?

Mfg Hajoseb
BenBE
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 8721
Erhaltene Danke: 191

Win95, Win98SE, Win2K, WinXP
D1S, D3S, D4S, D5E, D6E, D7E, D9PE, D10E, D12P, DXEP, L0.9\FPC2.0
BeitragVerfasst: So 24.06.07 18:00 
user profile iconChryzler hat folgendes geschrieben:
user profile iconBenBE hat folgendes geschrieben:
Er hat dabei einmal versucht, BucketSort zur Basis 2 zu implementieren und hat dabei seine Variante so umgeschrieben, dass er diese INLINE ausführen konnte (also direkt im zu sortierenden Array).

Und was bedeutet das? Zur Basis 2?

Das bedeutet im wesentlichen, dass der Algo anhand der Bitaufteilung sortiert, d.h. nur 2 Fächer zum Ablegen der sortierten Elemente benötigt.
user profile iconChryzler hat folgendes geschrieben:
Ist der Algo jetzt doppelt so schnell oder was? :o Was genau ist jetzt der Unterschied zum "normalen" Bucket-Sort?

Der Unterschied betrifft im Wesentlichen folgende Dinge:
1. Der Algorithmus benötigt außer 12 Bytes auf dem Stack und 5 Registern keinerlei externen Speicher.
2. Der für das Umsortieren der Elemente in die beiden Taschen benötigte Speicher wird dynamisch zugewiesen, indem Elemente getauscht werden.

_________________
Anyone who is capable of being elected president should on no account be allowed to do the job.
Ich code EdgeMonkey - In dubio pro Setting.
Horst_H
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1654
Erhaltene Danke: 244

WIN10,PuppyLinux
FreePascal,Lazarus
BeitragVerfasst: So 24.06.07 18:38 
Hallo,

durch die simple 0/1 unerscheidung muss man aber auch 32 mal komplett durch das Feld pflügen und umsortieren.

Gruß Horst
Delphi-Laie
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 1600
Erhaltene Danke: 232


Delphi 2 - RAD-Studio 10.1 Berlin
BeitragVerfasst: Di 30.06.09 23:05 
Die im Erstbeitrag aufgeführte Pascal-Befehlssequenz funktioniert auch bei mir, mit einer Änderung: In dieser Schleife

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
for i := N - 1 downto 0 do  
begin  
temp:=getbits(a[i],pass*m1,m1);  
b[count[temp]]:=a[i];  
count[temp]:=pred(count[temp])
end;


mußte ich den zweiten und den dritten Befehl vertauschen (also in der Reihenfolge anordnen, wie hier veröffentlicht), damit es fehlerfrei läuft.
Delphi-Laie
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 1600
Erhaltene Danke: 232


Delphi 2 - RAD-Studio 10.1 Berlin
BeitragVerfasst: So 30.08.09 12:59 
user profile iconGausi hat folgendes geschrieben Zum zitierten Posting springen:
Da hier alle von diesem Algorithmus so begeistert sind, will ich mal n paar Worte sagen, weswegen der in aller Regel nicht als Standard verwendet wird.

1. Der Speicherbedarf. Wurde schon angesprochen. "Normale" Sortierverfahren wie Bubblesort, Heapsort, Quicksort sind In-Situ-Verfahren. Sie benötigen zum Sortieren keinen zusätzlichen Speicher (außer ein paar Byte für die Swap-Variable). Der hier vorgestellte Algorithmus benötigt doppelt soviel Speicher.

2. Laufzeit. Der Algorithmus wird langsam, wenn die zu sortierenden Zwahlen sehr groß werden, da für jedes Bit/Byte/Buchstabe eine "Runde" durchgeführt wird. zur Verdeutlichung ein Beispiel:(*)
Quicksort benötigt im Mittel log(N)*N viele Vergleiche, um eine Folge mit N vielen Einträgen zu sortieren. Das sind bei 1024 Werten ungefahr 10.000 Vergleiche.
Dieser Algo hier braucht ca. (Anzahl der Stellen)*N viele Vergleiche. Wenn ich nun z.B. Wörter sortieren will, die jeweils 20 Zeichen haben, brauche ich bei einer Wortliste von 1024 Wörtern schon 20.000 Vergleiche - grob doppelt soviele wie Quicksort.

Das Verfahren ist schnell, gar keine Frage. Aber nur unter gewissen Voraussetzungen und mit einem recht hohen Speicherbedarf. Daher wird meist Quicksort genommen, da sich dieser in der Praxis sehr bewährt hat.

(*)@die theoretischen Informatiker hier: Mir ist klar, dass man so eigentlich nicht rechnen kann. Aber das Problem wird hoffentlich trotzdem klar.


Hallo Gausi, ich bezweifele Deine Kompetenz nicht. Allerdings relativieren sich m.E. einige Deiner Aussagen doch ein klein wenig, wenn man das hier liest.

Edit: Ergänzungen: Für n zu sortierende Elemente benötigt man eben n Bucketfüllungen, und da für n Elemente wenigstens log(n) Bits (oder andere wohlunterschiedene Zeichen) nötig sind und ebensoviele Schleifendurchläufe erfordern, sind wir bei diesem Algorithmus m.E. schon wieder in der n*log(n)-Klasse bzw. -Komplexität angelangt.

Dieses auch als Strait Radix Sort bezeichnete Sortierverfahren hat noch einen anderen Nachteil, es ist völlig "dumm" hinsichtlich der Struktur der zu sortierenden Elementemenge. Vor- und sogar vollständig sortierte Mengen werden nicht erkannt (im Gegensatz zu anderen Sortieralgorithmen, die diesbezüglich "intelligenter" sind), sondern es wird stur der Algorithmus durchgezogen, der damit immer eine ähnliche Laufzeit besitzt. Parallele: Heapsort hat den gleichen Nachteil, und daraufhin entwickelte Dijkstra eine Modifikation namens Smoothsort, die gegenüber (vor-)sortierten Mengen eine bessere Laufzeit vorweisen kann.

Edit 2: Weiterer Nachteil: Wird als internes Sortierverfahren ein spezieller Sortieralgorithmus (Bucketsort) verwendet, wie es zur Demonstration der Geschwindigkeit durchaus üblich ist, können nur Integerzahlen sortiert werden. Wichtig ist, daß ein allgemeiner Sortieralgorithmus, der beliebige, auch (nicht unbedingt mathematisch) komplexe Daten sortiert, intern sortiert.
Delphi-Laie
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 1600
Erhaltene Danke: 232


Delphi 2 - RAD-Studio 10.1 Berlin
BeitragVerfasst: Sa 07.11.09 17:56 
user profile iconHorst_H hat folgendes geschrieben Zum zitierten Posting springen:
Hallo,

durch die simple 0/1 unerscheidung muss man aber auch 32 mal komplett durch das Feld pflügen und umsortieren.

Gruß Horst


Und das müßte m.E. bedingen, daß dieser Sortieralgorithmus nicht die Komplexität "n" (wie die Bucketsorts), sondern leider doch höchstens die Komplexität n*log(n) (man benötigt (mindestens) log(n) Bits, hier also konkret und beispielhaft 32, zur Darstellung von n verschiedenen Zuständen) hat.

An alle Sortiergeschwindigkeitsfanatiker: Schaut Euch mal die neueste Version meines Programmes "Sortierkino" an. Dort implementierte ich AVL- und B-Sort (die natürlich nicht von mir stammen, sondern von Peter Weigel von sortieralgorithmen.de). Die stellen nicht nur das hier thematisierte Strait Radix Sort, sondern auch Heap- und Mergesort und sogar das Quicksort in den Schatten!
alzaimar
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 2889
Erhaltene Danke: 13

W2000, XP
D6E, BDS2006A, DevExpress
BeitragVerfasst: Sa 07.11.09 23:40 
Das deckt sich nicht mit meinen Erkentnissen. Anstelle des relativ langsamen AVL-Baumes habe ich eine SkipList verwendet, um die Daten sortiert aufzubereiten. Das sollte um ein vielfaches Schneller sein (jedenfalls ist es das bei meinem Vergleich von Datenstrukturen zum Einfügen und Suchen von Objekten)
www.delphipraxis.net...gleich+suchverfahren

Ich habe an anderer Stelle, bei der es um eine Sortierung von Strings ging, vorgeschlagen, die Strings zunächst in eine Skiplist zu schreiben (dann sind sie sortiert) und sie dann auszulesen. Das hat dort wunderbar funktioniert, aber auch nur deshalb, weil die Vergleichsoperation der Strings sehr langsam war. Der Trick funktioniert nicht bei normalen Anwendungen. Da bei mir die Skiplist viel schneller als der AVL-Baum ist, schlußfolgere ich daraus, das eine Sortierung mit Hilfe des AVL-Baumes noch schlimmer abschneidet.

Im Vergleich zu einem generischen Quicksort sind (bei mir) bisher alle anderen Verfahren langsamer. Das liegt aber nur daran, das Quicksort kompakt ist und kaum Overhead produziert. Alleine die Verwendung von komplexeren Strukturen (AVL-Sort, B-Sort usw.) frisst nach meinen Untersuchungen den Vorteil des eigentlich besseren Verfahrens auf.

Mich würde interessieren, wie sich die angeblich so superschnellen Sortieralgorithmen in der Praxis verhalten. Dein Sortierkino lässt leider keinen echten Performancevergleich zu: Hier sind die AVL- und B-Sortierungen auch visuell schneller, weil die 'Bremse' nur beim Zeichnen der Linien greift, nicht jedoch bei den Vorbereitungen (Schreiben in die Datenstruktur).

_________________
Na denn, dann. Bis dann, denn.