Entwickler-Ecke

Algorithmen, Optimierung und Assembler - Zufallszahlen in Assembler


Mathematiker - So 16.09.12 09:59
Titel: Zufallszahlen in Assembler
Hallo,
nachdem ich bei der Darstellung eines Plasmas (http://www.entwickler-ecke.de/topic_110170.html) mit Assembler experimentiert habe, wollte ich das etwas besser verstehen.
Aus diesem Grund habe ich die zwei Assembler-Routinen


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:
    //Zufallszahl im Intervall [0, a-1]
    function randomx(a:integer):integer;
    asm
       xor edx, edx      
       imul edx, [randseed], $08088405
       inc edx
       mov [randseed], edx
       mul edx
       mov eax, edx       //in eax steht zufallswert
    end;

    //Zufallszahl im Intervall [-a, a]
    function randomy(a:integer):integer;
    asm
       push eax             //a sichern
       shl eax, 1
       add eax, 1
       xor edx, edx
       imul edx, [randseed], $08088405
       inc edx
       mov [randseed], edx
       mul edx
       pop eax              
       sub eax, edx       //in eax steht zufallswert
    end;

mit den normalen Delphi-Aufrufen random(a) bzw. random(2*a+1)-a verglichen. Dabei ist die Grundidee des ASM-Textes nicht von mir, sondern aus http://www.entwickler-ecke.de/viewtopic.php?t=77045&highlight=random+asm entnommen.

Nun geschieht etwas, meiner Meinung nach, sehr Merkwürdiges.
Während die erste Funktion sehr oft schneller ist als random(a), aber nicht immer(?), ist die zweite nur selten besser als random(2*a+1)-a, mitunter sogar langsamer.
Das verstehe ich nicht. :nixweiss: Durch das Übernehmen der Multiplikation mit 2, der Additon mit 1 und am Ende der Subtraktion mit a direkt(!) im Assembler-Code müsste es doch gerade hier einen Geschwindigkeitszuwachs geben.

Bei mehreren Tests ergab sich im Mittel

Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
Zufallsintervall [0, a-1]
Delphi  0,198 s
ASM  0,121 s
Gewinn  38,8 %
Zufallsintervall [-a, a]
Delphi  0,127 s
ASM  0,128 s
Gewinn  -0,35 %

Als ich meinen Prozessor Genuine Intel(R) T2250 1,73 GHz mit nur 50 %-Leistung (Energiesparplan) genutzt habe, wurde

Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
Zufallsintervall [0, a-1]
Delphi  0,271 s
ASM  0,264 s
Gewinn  2,4 %
Zufallsintervall [-a, a]
Delphi  0,278 s
ASM  0,279 s
Gewinn  -0,56 %

Und das verstehe ich nun gar nicht mehr. Wieso ist Assembler bei 100% CPU-Auslastung auf einmal so viel schneller im Vergleich zu Delphi?

Vielleicht kann mir jemand von Euch helfen.

Beste Grüße
Mathematiker


uall@ogc - So 16.09.12 19:31

Du kannst das Push eax / Pop Eax auch noch weglassen, im Grunde ist dies aber nichts anderes als die Random-Funktion von Delphi (wenn ich mich nicht irre), von daher sollte es keinen Unterschied machen, eine Zeitmessung ueber 0.2 ist da nicht aussagekraeftig.

Verwende einfach mal das Delphi-Random mach nen Breakpoint drauf und schau dir den code an (glaub strg+shift+c).


Mathematiker - So 16.09.12 19:46

Hallo
user profile iconuall@ogc hat folgendes geschrieben Zum zitierten Posting springen:
Du kannst das Push eax / Pop Eax auch noch weglassen, im Grunde ist dies aber nichts anderes als die Random-Funktion von Delphi (wenn ich mich nicht irre)

Es ist die Random-Funktion von Delphi. Push und Pop kann ich leider nicht weglassen, da ich einen Fehler im Text hatte. Richtig ist:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
    function randomy(a:integer):integer;
    asm
       push eax
       shl eax, 1
       add eax, 1
       xor edx, edx
       imul edx, [randseed], $08088405
       inc edx
       mov [randseed], edx
       mul edx
       mov eax, edx
       pop edx
       sub eax, edx       //in eax steht zufallswert [-a,a]
    end;

user profile iconuall@ogc hat folgendes geschrieben Zum zitierten Posting springen:
von daher sollte es keinen Unterschied machen, eine Zeitmessung ueber 0.2 ist da nicht aussagekraeftig.

Danke für den Hinweis. Ich habe die Schleifendurchläufe immer mehr erhöht, wobei der Unterschied zwischen Delphi und Assembler immer kleiner wurde. Die Delphi-Lösung ist wohl schon optimal.
Beste Grüße
Mathematiker


OlafSt - Mo 17.09.12 00:21

Ich habe spaßenshalber mal den Code von random(x) auf Assembler-Ebene betrachtet. Die oben vorgeschlagene Routine ist eine 1:1-Kopie davon ;)

Das Laufzeitverhalten kann durchaus unterschiedlich sein. Die Befehle IMUL und MUL haben in den Opcode-Tabellen von Intel stets eine Laufzeit von "xx-yy Takte" angegeben, bis Intel keine Taktangaben mehr ausgab (siehe weiter unten, warum). So ist es dann möglich, das hunderte IMULs xx Takte brauchen, und ein dutzend weitere plötzlich yy Takte. MUL hat sogar dreistellige Taktzahlen IIRC. Daher die unterschiedlichen Laufzeiten. Um herauszufinden, wann genau MUL/IMUL wieviel Takte benötigt, ist ein Blick in die Opcode-Tabelle von Intel hilfreich - ich hab mir das nicht so genau angesehen ;)

Die Tatsache, das im Stromsparmodus die Laufzeiten in die Höhe schnellen, ist einfach erklärt. Um Strom zu sparen, werden Teile der CPU deaktiviert bzw. mit sehr niedrigen Taktraten betrieben - die FPU ist z.B. so ein Kandidat, aber auch Teile der ALU werden abgeschaltet, so das nur mehr 5 oder 6 ALUs laufen, und das restliche dutzend schläft. So müssen sich nun die Mnemonics durch 6 ALUs quetschen, statt bequem und verzögerungsfrei superskalar abgearbeitet werden zu können. Und schwups dauert die Berechnung 80% länger - dafür läuft der Notebook aber 10 Minuten länger.

Heutige CPUs haben mit den simplen Prozessoren, auf denen wir mal gelernt haben (ich hab auf nem 6510, dann Z80 und dann 8085 angefangen ;)) nur noch den Namen gemein. Tatsächlich ist es heute praktisch unmöglich, die exakte Laufzeit einer Routine konkret zu berechnen, weshalb bei Intel auch kaum noch Taktangaben zu finden sind - superskalar sei Dank. Da ist es möglich, das Befehle auch Null Takte Laufzeit benötigen...