Put-Pixel-Routine beschleunigen



  • Was bitte ist an Optimierungen - egal welcher Art - falsch?? Gerade eine so elementare Funktion wie die putPixel sollte möglichst schnell sein, besonders dann, wenn sie oft verwendet wird.
    Klar, ist ein gutes Konzept wichtiger, aber gerade da soviele auf Feintuning verzichten, sind die Spiele von heute viel langsamer als sie sein müßten.

    Übrigens gabs hier schonmal eine vergleichbare Diskussion um putPixel.



  • Original erstellt von <Steffen Vogel>:
    Könntest du mir den genauen nötigen Code dafür schreiben, wie müsste das in der C-Fkt aussehen ?

    void fast_put_pixel(int x, int y, int color, unsigned char *adr)
    {
      *(adr+x+((y<<8)+(y<<6)))=color;
    }
    

    so würde das in C aussehen.
    Aber dazu noch was interessantes, dass macht der gcc 3.0.4 aus dem put_pixel

    put_pixel:
        pushl   %ebp
        movl    %esp, %ebp
        movl    12(%ebp), %eax
        leal    (%eax,%eax,4), %eax
        sall    $6, %eax
        addl    20(%ebp), %eax
        movl    16(%ebp), %ecx
        movl    8(%ebp), %edx
        movb    %cl, (%edx,%eax)
        popl    %ebp
        ret
    

    und das aus fast_put_pixel (!)

    fast_put_pixel:
        pushl   %ebp
        movl    %esp, %ebp
        movl    12(%ebp), %edx
        movl    %edx, %eax
        movl    20(%ebp), %ecx
        sall    $6, %eax
        sall    $8, %edx
        addl    8(%ebp), %ecx
        addl    %eax, %edx
        movl    16(%ebp), %eax
        movb    %al, (%edx,%ecx)
        popl    %ebp
        ret
    

    (noch mein Senf zur Welt:

    In der Regel optimieren Compiler schon verdammt gut, nur bei wirklich harten Dingen sollte man Assembler einsetzen, aber da fällt mir spontan nichts ein ;))



  • hi,

    wie ruft man die funktion:
    void fast_put_pixel(int x, int y, int color, unsigned char *adr)
    {
    *(adr+x+((y<<8)+(y<<6)))=color;
    }
    dann auf?
    die ersten drei parameter sind klar, aber was soll ich da für nen pointer übergeben?



  • Hi!

    Ich bin zwar schon ne ganze Weile weg vom Assembler, aber damals habe ich das etwas anders gemacht. Ich habe nämlich ein Zeilen-Array reserviert, in dem die Start-Adresse jeder Grafikzeile stand.
    Also war z.B.:

    grRowArr[0] == 0
    grRowArr[1] == 320
    grRowArr[0] == 640

    usw. Das geht dann ein bissl schneller als Multiplikation und auch als Shifts. Wollt ich nur mal so ansprechen, weil diese Möglichkeit hier noch niemand erwähnt hat 🙂 ... und der Gra-Speed hat da rult 😃

    @BlockBuster: adr dürfte hier 0xA000h bzw Start des GrafikScreens sein.



  • Wohl kaum schneller als Shifts. Ein shl eines 16-Bit-Registers brauchte schon auf dem 8086 nur 2 Takte, wobei ein add eines 16-Bit-Registers mit einer Speicherstelle darauf mehr als 9 Takte brauchte. Auf nem Pentium war das also höchstens gleich schnell (je 1 Takt 😉 ), wobei allerdings nach deiner Methode 200 Bytes fürs Array verbraten werden. 🙄

    [ Dieser Beitrag wurde am 06.06.2002 um 18:47 Uhr von Cocaine editiert. ]



  • Bleiben wir doch mal realistisch: 8086 😕
    Fangen wir mal mit 486er an. Ich poste mal eben PseudoCode und zwar für PM. RM kann ja umsetzen wer will:

    ; =============================
    @Putpixel:
    ; =============================
    ; IN: EAX= X-Position
    ;     ECX= Y-Position
    ;     BL = Color
    
       add   eax, [ecx*2+arrOfRows]; Oder vorher shl ecx, 2 ... ich kenn jetzt die clocks net
       add   eax, [videoAdress]    ; On Screen
       mov   [eax], bl             ; set color
       ret
    

    Hier gehe ich natürlich von einem FlatSegment und CS=DS aus. Ich spare hier übrigens nicht nur ein Shift sondern 2. Und zusätzlich noch Additionen. Ausserdem sche|ss ich erlich gesagt prinzipiell auf extra reservierten Speicher wenn man Speed dafür rausholen kann 🙂 .

    Falls es trotzdem Blödsinn ist, dann sorry und belehr mich - bin schon zu lang raus 😃 . Aber ich errinnere mich gut, daß ich das Tage lang abgewogen und mich dann dafür entschieden habe 🕶 .



  • @Nubuo T: Hast du eine Liste der Taktzyklen der einzelnen Befehle ?



  • @blockbuster: Die Funktion stellt einen Punkt an der Stelle eines Speichers dar, dessen Adresse du ihr mit adr übergibst. Somit kannst du entweder direkt in den Videospeicher schreiben oder in einen Puffer, den man dann später ins Videoram kopiert. Sowas macht zum Beispiel Sinn, wenn man mehrere Punkte zeichnen lassen will, aber das Bild auf einmal erscheinen soll und nicht punktweise.
    Ich hoffe, du verstehst jetzt wie du diese Fkt benutzt.



  • Ich habe eine Befehlsreferenz, bei der uA. auch die benoetigte Anzahl von Tacktzyklen pro Befehl drinstehen. Leider ist das ein Buch, also wirds schwer mit dem Posten o.ae.
    Kauf dir den TASM von Borland. Da ist diese Befehlsreferenz und noch ein Haufen anderes Zeug mit bei 😉



  • @Steffen Vogel achso... naja und welche adresse muss ich jetzt übergeben wenn ich was direkt auf den bildschirm malen will?



  • Dafür musst du die Adresse des Bildschirmspeichers nehmen.
    Ich mache das immer so:

    unsigned char _far *vidmem = (unsigned char _far *)0xA0000000;

    Und dann rufe ich put_pixel(10,10,5,vidmem);

    auf, damit ein Punkt bei 10,10 mit der Farbe 5 gemalt wird.

    Also Puffer würde man halt vorher definieren:

    unsigned char puffer[64000];

    und dann entsprechend

    put_pixel(10,10,5,puffer);

    aufrufen.

    Um das Bild in den Bildschirmspeicher zu kopieren mache ich:
    movedata(FP_SEG(puffer),FP_OFF(puffer),FP_SEG(vidmem),FP_OFF(vidmem),64000);

    Ich hoffe ich konnte deine Frage klären und wünsche dir viel Spaß beim Ein- und Umsetzen der neuen Kenntnisse.



  • @Steffen Vogel hey, danke! 🙂 jetzt weiß ich alles was ich wissen will, bis auf eins 🙂
    die funktion movedata kann ich ja net aufrufen wenn ich unter keinem OS arbeite... wie kann ich das dann machen?



  • LOL, erzähl jetzt bitte nicht - Du willst ein OS proggen und weisst nicht mal wie man Daten kopiert 😮



  • @VollTroll-Germania doch schon... aber ich weiß net genau was die movedata funktion macht



  • Sie kopiert den Speicherinhalt mit der Länge die du ihm als letzten Parameter angibst. Die Fkt.:FP_SEG ermittelt zum Beispiel die Segment-Adresse fürs Kopieren. Sonst noch fragen ?



  • Macht euch doch mal klar was ihr da vorhabt. Ihr wollt 1 byte in den speicher
    schreiben. Und ihr verwendet dazu allen ernstes eine eigene Funktion und meint diese sei
    schneller wenn ihr shiftet oder gar alles in asm schreibt.

    Das hier hat vc aus der funktion gemacht:
    void put_pixel(int x, int y, int color, unsigned char *adr) {
    (adr+x+(y320))=color;
    }

    lea eax,dword ptr [edx+edx*4]
    mov edx,dword ptr [esp+8]
    shl eax,6
    add eax,edx
    mov dl,byte ptr [esp+4]
    mov byte ptr [eax+ecx],dl
    ret

    Nochmal: <1 byte in den speicher schreiben!> M$´s VC optimiert sogar den stackframe weg
    und scheint nen _fastcall aus der funktion gemacht zu haben.
    Bleiben aber immernoch stackoperationen(call,ret) übrig und noch dazu wird der IP
    verändert(call). Wisst ihr was ein push im schlimmsten fall mit dem cache anrichten kann.
    Das kann u.U. länger dauer als eure gesammte optimierte funktion. Und ein call spült die
    Prefetch-Queue das klo runter. Also weg mit der Funktion(kein call, kein ret, kein push):

    #define put_pixel(x,y,color,adr) ((adr+x+(y320))=color)

    z.B:
    put_pixel(10,10,128,buff);
    ..erzeugt..
    mov byte ptr [_buff(0x00414c0a)+0C8Ah],80h

    oder:
    int x;
    int y=10;
    int col=128;
    for(x=0;x<10;x++)
    put_pixel(x,y,col,buff);

    ..erzeugt..

    mov eax,80808080h
    mov [_buff(0x00414c00)+0C80h],eax
    mov [_buff(0x00414c04)+0C84h],eax
    mov [_buff(0x00414c08)+0C88h],ax

    Und man braucht nicht einmal shiften, das macht der alles von selbst wenn nötig. Und noch
    viel besser, jedesmal wenn das makro verwendet wird erzeugt der compiler eine komplett
    angepasste version die sich wunderbar in den umgebende code (schleife, if-statement)
    anpasst. Und das ganz ohne ASM.

    Mal ehrlich glaubt ihr wirklich M$ verlangt solche preise nur für die
    entwicklungsumgebung?? Editor mit syntaxhighlight gibt es schon für umsonst.

    Ich meine es ist um einiges effizienter wenn man lernt, wie man seinem Compiler die
    richtige vorlage gibt anstatt ihm gleich zu mistrauen und anfängt takte zu zählen.

    Das soll aber nicht bedeuten das asm optimieren völlig unnötig sei, man denke da nur an
    MMX un Co., aber in den anderen fällen sollte das nur nötig sein wenn das projekt fast
    fertig ist und einige profiler läufe anlass dazu geben. z.B.: ist putPixel wirklich dafür
    verantwortlich das das prog. nicht schnell genug läuft?

    MFG
    mondo



  • Original erstellt von mondo:
    **...
    **

    *zustimm*
    Aber bitte nimm das #define zurück und sag inline.



  • @mondo: War ja nett gemeint, aber vielleicht solltest du auch die Ausgabe des Programms dann mal kontrollieren. Mein Programm läuft damit keinen Deut schneller, aber dafür wird die Darstellung falsch, alle Ausgaben landen in der ersten Zeile. Da probiere ich es doch lieber mal mit shiften ! Trotzdem danke



  • Aber auch shiften macht das Programm nicht schneller. Wie kingruedi schon zeigte, erzeugt der Compiler sehr viel mehr Befehle beim shiften ! Vielleicht hebt sich dadurch der Vorteil von shift gegenüber den Multiplikationen wieder auf.



  • <ist putPixel wirklich dafür verantwortlich das das prog. nicht schnell genug läuft?>

    Es ging mir ja auch erst mal ums prinzip. Wenn des Macro nicht läuft, dann korrigier es doch. Aber wenn Du meinst eine funktion sei schneller ... dann nimm halt ne funktion. 😃

    happy coding 🙄


Anmelden zum Antworten