Autor Beitrag
user32
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 55
Erhaltene Danke: 5



BeitragVerfasst: Mo 09.05.11 02:52 
Also, es geht hier um Optimierung einer Bitmap + Scanline Routine. Das Ganze hat weniger einen praktischen Zweck sondern ist eher zum Lernzweck für mich, da ich noch relativer Newbie bin in Assembler.

Also... Im Moment dauert es 5,8 ms um ein 1100x800 Bild mit gelb-rotem Rauschen zu füllen. Noch wichtiger: Die Funktion braucht knapp 10,7 Mio Takte (gemessen). Also 12 Takte/Pixel.. naja. Immerhin 40% schneller als nur mit Delphi. Aber das reicht mir noch nicht ;)
Ich hab schon soweit ich konnte versucht zu optimieren, aber ich denke mal da geht noch was. Sind doch gradmal <1Mio Pixel :)

Vielleicht hat ja jemand ne Idee.
Evtl. irgendwelche Opcodes die weniger Takte brauchen...?
Thx! ;)

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:
p := _image.ScanLine[height-1];   // der aufruf allein dauert schon 2ms :(
    asm
        push    EBX
        push    ECX
        push    EDX

        rdtsc         // zum takte zählen
        mov     DWORD PTR[time1+$4],EDX  // 64bit variable
        mov     DWORD PTR[time1+$0],EAX  //

        mov     EAX,width
        mov     ECX,height
        imul    ECX,EAX         // anzahl der pixel
        xor     EAX,EAX         // (farbe: schwarz) nicht mehr
        mov     EDX,[p]         // EDX: adresse der pixel
        imul    EBX,ECX,3       // EBX = 3 byte x anzahl pixel      


        @@again:               // Hauptschleife. habe schon versucht, anstatt 3x BYTE-zugriffe 
                               //1x DWORD zu nehmen, allerdings war die geschwindigkeit unverändert. 
        sub     EBX,3

        call    RandX             // eigene random-funtkion; ist schon 100% optimiert
        mov     BYTE PTR[EDX+EBX+$00],$00
        mov     BYTE PTR[EDX+EBX+$01],AL
        mov     BYTE PTR[EDX+EBX+$02],AH
        loop @@again



        rdtsc
        mov     DWORD PTR[time2+$4],EDX
        mov     DWORD PTR[time2+$0],EAX

        pop     EDX
        pop     ECX
        pop     EBX
    end;
Gammatester
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 328
Erhaltene Danke: 101



BeitragVerfasst: Mo 09.05.11 11:27 
user profile iconuser32 hat folgendes geschrieben Zum zitierten Posting springen:
Also... Im Moment dauert es 5,8 ms um ein 1100x800 Bild mit gelb-rotem Rauschen zu füllen. Noch wichtiger: Die Funktion braucht knapp 10,7 Mio Takte (gemessen). Also 12 Takte/Pixel.. naja. Immerhin 40% schneller als nur mit Delphi. Aber das reicht mir noch nicht ;)
Ich hab schon soweit ich konnte versucht zu optimieren, aber ich denke mal da geht noch was. Sind doch gradmal <1Mio Pixel :)

Vielleicht hat ja jemand ne Idee.
Wenn man sich die Schleife ansieht, wird doch wohl die meiste Zeit mit call RandX verbraucht!? Wie sind denn die Werte, wenn diese Anweisung auskommentiert wird?

Selbst wenn es eigentlich wenig sinnvoll ist, Pseudo-Zufallsrauschen zu malen, kann man auf jedenfall Zyklen sparen, wenn der Code für RandX direkt in die Schleife eingebaut wird (selbst wenn RandX 100% optimiert ist - was auch immer das bedeutet). Ein zweiter Ansatz wäre Loop-Unrolling. Eventuell ist auch (je nach Prozessor) sub ecx,1 / jnz @@again schneller als loop @@again.
bummi
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 1248
Erhaltene Danke: 187

XP - Server 2008R2
D2 - Delphi XE
BeitragVerfasst: Mo 09.05.11 12:59 
hat zwar mit ASM nicht zu tun, aber wenn Du statt für jede Zeile Scanline abzufragen direkt über die letzte Scanline mit berectneten Offsets über Höhe und Breite zugreifst kannst nochmals ordentlich Performance rausholen, Anhang ist zwar Delphi, aber das Zugriffsprinzip beleibt das gleiche
ausblenden 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:
Procedure FastPixel(BMP:TBitmap;LastScanLine:PCArray;x,y:Integer;Farbe:TColor);
begin
   LastScanLine[(Bmp.Height - y) * Bmp.Width  + x]:= Color2RGB(Farbe);
end;
// Wahlfreier Zugriff über zuvor bestimmte letzte Scanline 47 ms
procedure TForm2.Button4Click(Sender: TObject);

var
  x,y,w:Integer;
  tc:Cardinal;
  CArray,CArray2:PCArray;
begin
    tc := GetTickCount;
    image1.Canvas.FillRect(image1.clientRect);
    image1.picture.Bitmap.PixelFormat := pf32bit;
    w := Image1.Picture.Bitmap.Width;
    CArray := Image1.Picture.Bitmap.ScanLine[Image1.Height - 1];

    for y := 1 to image1.picture.Bitmap.Height -1 do
      begin
        for x := 0 to image1.picture.Bitmap.Width -1 do
          begin
               FastPixel(image1.picture.Bitmap,CArray,x,y,clBlue);
          end;
      end;
    Caption := IntToStr(GetTickCount-tc);
end;

_________________
Das Problem liegt üblicherweise zwischen den Ohren H₂♂
DRY DRY KISS
Narses
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Administrator
Beiträge: 10183
Erhaltene Danke: 1256

W10ent
TP3 .. D7pro .. D10.2CE
BeitragVerfasst: Mo 09.05.11 14:25 
Moin!

user profile iconGammatester hat folgendes geschrieben Zum zitierten Posting springen:
Wenn man sich die Schleife ansieht, wird doch wohl die meiste Zeit mit call RandX verbraucht!?
Man kann mit rückgekoppelten Schieberegistern Pseudozufallszahlen erzeugen, das sollte das schnellste Verfahren sein, wenn es nicht auf die Güte ankommt. ;) :idea:

cu
Narses

_________________
There are 10 types of people - those who understand binary and those who don´t.
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19314
Erhaltene Danke: 1747

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Mo 09.05.11 15:18 
user profile iconbummi hat folgendes geschrieben Zum zitierten Posting springen:
hat zwar mit ASM nicht zu tun, aber wenn Du statt für jede Zeile Scanline abzufragen direkt über die letzte Scanline mit berectneten Offsets über Höhe und Breite zugreifst kannst nochmals ordentlich Performance rausholen
Deine Version ist langsamer als seine. Denn er rechnet gar nichts, sondern geht immer nur ein Pixel weiter. ScanLine wird nur am Anfang einmal aufgerufen. Du jedoch rechnest jedesmal beim Pixelzugriff, das sollte deutlich langsamer sein. ;-)
user32 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 55
Erhaltene Danke: 5



BeitragVerfasst: Mo 09.05.11 15:29 
Hi,
user profile iconbummi hat folgendes geschrieben Zum zitierten Posting springen:
hat zwar mit ASM nicht zu tun, aber wenn Du statt für jede Zeile Scanline abzufragen direkt über die letzte Scanline mit berectneten Offsets über Höhe und Breite zugreifst kannst nochmals ordentlich Performance rausholen, Anhang ist zwar Delphi, aber das Zugriffsprinzip beleibt das gleiche

Ich greife doch nur einmal drauf zu! Oder meinst du Scanline VORHER abzufragen und in einer globalen Variable speichern?

user profile iconGammatester hat folgendes geschrieben Zum zitierten Posting springen:
Wenn man sich die Schleife ansieht, wird doch wohl die meiste Zeit mit call RandX verbraucht!? Wie sind denn die Werte, wenn diese Anweisung auskommentiert wird?

Selbst wenn es eigentlich wenig sinnvoll ist, Pseudo-Zufallsrauschen zu malen, kann man auf jedenfall Zyklen sparen, wenn der Code für RandX direkt in die Schleife eingebaut wird (selbst wenn RandX 100% optimiert ist - was auch immer das bedeutet).

Das ist eine gute Idee! RandX wird noch von anderen Prozeduren gebraucht, deswegen hab ich garnicht dran gedacht sie hier in den Code einzugliedern! Also RandX sieht bis jetzt so aus:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
function RandX:DWORD;
asm
        push    EDX
        push    ECX

        mov     EAX,RandSeed        // (in FormCreate wird außerdem Randomize aufgerufen)
        mov     ECX,RandSeed        // EAX und ECX = seed

        imul    EDX,ECX,87654321h   // Seed * irgendwas -> EDX
        mov     RandSeed,EDX        // neuer seed zurück
        mul     EDX                 // EAX*EDX
        mov     EAX,EDX

        pop     ECX
        pop     EDX
end;

Ich werd sie gleich mal in den Loop integrieren. Vielleicht kann ich ja die Pushs und Pops loswerden.

user profile iconGammatester hat folgendes geschrieben Zum zitierten Posting springen:

Ein zweiter Ansatz wäre Loop-Unrolling. Eventuell ist auch (je nach Prozessor) sub ecx,1 / jnz @@again schneller als loop @@again.

Jnz hab ich schon probiert. War ganze 10ms langsamer...warum auch immer. :crying:

user profile iconNarses hat folgendes geschrieben Zum zitierten Posting springen:
Moin!

user profile iconGammatester hat folgendes geschrieben Zum zitierten Posting springen:
Wenn man sich die Schleife ansieht, wird doch wohl die meiste Zeit mit call RandX verbraucht!?
Man kann mit rückgekoppelten Schieberegistern Pseudozufallszahlen erzeugen, das sollte das schnellste Verfahren sein, wenn es nicht auf die Güte ankommt. ;) :idea:

cu
Narses

Ich werds mir gleich mal angucken!

Mfg

---Moderiert von user profile iconNarses: Beiträge zusammengefasst---

4,5 Millisek bzw. 7,1 Millionen clock cycles.
Hm ja, mit dem LFSR bin ich mir nicht sicher, ob ich es richtig gemacht habe. Sieht jedenfalls ziemlich zufällig 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:
43:
44:
45:
46:
47:
48:
49:
50:
p := _image.ScanLine[height-1]; 
asm
        push    EBX
        push    ECX
        push    EDX
        push    ESI
        push    EDI

        rdtsc         // zum takte zählen
        mov     DWORD PTR[time1+$4],EDX  // 64bit variable
        mov     DWORD PTR[time1+$0],EAX  //


        mov     EAX,width
        mov     ECX,height
        imul    ECX,EAX         // anzahl der pixel
        mov     EDX,[p]         // EDX: adresse der pixel
        imul    EBX,ECX,3       // EBX = 3 byte x anzahl pixel

        mov     ESI,EDX         // kein PUSH EDX mehr = schneller


        @@again:                // Hauptschleife.
        sub     EBX,3
        (*random:  keine ahnung, ob "LFSR" richtig ist, aber es sieht zufällig aus :)  *)
          mov       EAX,RandSeed
          mov       EDI,$409
          xor       EAX,EDI
          rol       EAX,1
          INC       EAX
          mov       RandSeed,EAX
          mul       EDI
        (*random end*)
        mov     BYTE PTR[ESI+EBX+$00],$00
        mov     BYTE PTR[ESI+EBX+$01],AL
        mov     BYTE PTR[ESI+EBX+$02],AH
        loop @@again



        rdtsc
        mov     DWORD PTR[time2+$4],EDX
        mov     DWORD PTR[time2+$0],EAX

        pop     EDI
        pop     ESI
        pop     EDX
        pop     ECX
        pop     EBX
end;