Bereich möglichst schnell mit Werten füllen



  • Ich hab eine Grafikengine in Assembler und C geschrieben
    und überlege nun wie ich am schnellsten den Bildschirm leere,
    also den Buffer komplett mit 0en überschreibe
    (das muss leider in jedem Frame wieder gemacht werden, kostet also ziemlich Zeit)

    im Moment hab ich eine umständliche loop Funktion genommen,
    aber ich denke mit rep geht das noch irgendwie schneller

    als nächstes bräuchte ich dann auch noch eine Funkion die einen Bereich möglichst schnell an eine andere Stelle im Speicher kopiert
    (hab ich im Moment auch mit einer loop Funktion gelöst),
    ich hab gehört, dass das aufjedenfall mit rep gehen soll, wie weis ich allerdings nicht

    vielleicht kann mir jemand von euch auf die Sprünge helfen

    EDIT:

    okay, hat sich erledigt, ging mit stosw und movsw ganz einfach

    EDIT2:

    na toll, hab jetzt die Zeit gemesen, meine loop-Konstruktion ist fast doppelt so schnell
    und ich dacht immer rep wär performant



  • haben deine rep-befehle nur 16-bitter geschrieben und deine eigene loop gleich 32-bitter?



  • Ich hoffe mal das geht klar mit Copyrightvermerk ^^
    Vielleicht findest du etwas, was dir hilft. _VEC_memzero habe ich allerdings nicht gefunden.

    page    ,132
            title   memset - set sections of memory all to one byte
    ;***
    ;memset.asm - set a section of memory to all one byte
    ;
    ;       Copyright (c) Microsoft Corporation. All rights reserved.
    ;
    ;Purpose:
    ;       contains the memset() routine
    ;
    ;*******************************************************************************
    
            .xlist
            include cruntime.inc
            .list
    
    page
    ;***
    ;char *memset(dst, value, count) - sets "count" bytes at "dst" to "value"
    ;
    ;Purpose:
    ;       Sets the first "count" bytes of the memory starting
    ;       at "dst" to the character value "value".
    ;
    ;       Algorithm:
    ;       char *
    ;       memset (dst, value, count)
    ;               char *dst;
    ;               char value;
    ;               unsigned int count;
    ;               {
    ;               char *start = dst;
    ;
    ;               while (count--)
    ;                       *dst++ = value;
    ;               return(start);
    ;               }
    ;
    ;Entry:
    ;       char *dst - pointer to memory to fill with value
    ;       char value - value to put in dst bytes
    ;       int count - number of bytes of dst to fill
    ;
    ;Exit:
    ;       returns dst, with filled bytes
    ;
    ;Uses:
    ;
    ;Exceptions:
    ;
    ;*******************************************************************************
    
            CODESEG
    
        extrn   _VEC_memzero:near
        extrn   __sse2_available:dword
    
            public  memset
    memset proc \
            dst:ptr byte, \
            value:byte, \
            count:dword
    
            OPTION PROLOGUE:NONE, EPILOGUE:NONE
    
            .FPO    ( 0, 3, 0, 0, 0, 0 )
    
            mov     edx,[esp + 0ch] ; edx = "count"
            mov     ecx,[esp + 4]   ; ecx points to "dst"
    
            test    edx,edx         ; 0?
            jz      short toend     ; if so, nothing to do
    
            xor     eax,eax
            mov     al,[esp + 8]    ; the byte "value" to be stored
    
    ; Special case large block zeroing using SSE2 support
        test    al,al ; memset using zero initializer?
        jne     dword_align
        cmp     edx,0100h ; block size exceeds size threshold?
        jb      dword_align
        cmp     DWORD PTR __sse2_available,0 ; SSE2 supported?
        je      dword_align
    
        jmp     _VEC_memzero ; use fast zero SSE2 implementation
        ; no return
    
    ; Align address on dword boundary
    dword_align:
    
            push    edi             ; preserve edi
            mov     edi,ecx         ; edi = dest pointer
    
            cmp     edx,4           ; if it's less then 4 bytes
            jb      tail            ; tail needs edi and edx to be initialized
    
            neg     ecx
            and     ecx,3           ; ecx = # bytes before dword boundary
            jz      short dwords    ; jump if address already aligned
    
            sub     edx,ecx         ; edx = adjusted count (for later)
    adjust_loop:
            mov     [edi],al
            add     edi,1
            sub     ecx,1
            jnz     adjust_loop
    
    dwords:
    ; set all 4 bytes of eax to [value]
            mov     ecx,eax         ; ecx=0/0/0/value
            shl     eax,8           ; eax=0/0/value/0
    
            add     eax,ecx         ; eax=0/0val/val
    
            mov     ecx,eax         ; ecx=0/0/val/val
    
            shl     eax,10h         ; eax=val/val/0/0
    
            add     eax,ecx         ; eax = all 4 bytes = [value]
    
    ; Set dword-sized blocks
            mov     ecx,edx         ; move original count to ecx
            and     edx,3           ; prepare in edx byte count (for tail loop)
            shr     ecx,2           ; adjust ecx to be dword count
            jz      tail            ; jump if it was less then 4 bytes
    
            rep     stosd
    main_loop_tail:
            test    edx,edx         ; if there is no tail bytes,
            jz      finish          ; we finish, and it's time to leave
    ; Set remaining bytes
    
    tail:
            mov     [edi],al        ; set remaining bytes
            add     edi,1
    
            sub     edx,1           ; if there is some more bytes
            jnz     tail            ; continue to fill them
    
    ; Done
    finish:
            mov     eax,[esp + 8]   ; return dest pointer
            pop     edi             ; restore edi
    
            ret
    
    toend:
            mov     eax,[esp + 4]   ; return dest pointer
    
            ret
    
    memset  endp
    
            end
    


  • haben deine rep-befehle nur 16-bitter geschrieben und deine eigene loop gleich 32-bitter?

    das ganze läuft im 16-Bit Modus deswegen benutze ich ja auch stosw und nicht stosd und mein loop ist auch 16-Bit

    ich hab das Ganz aber ungefähr so wie in Nekus Beispiel gemacht,
    vielleicht ist die rep Möglichkeit einfach langsamer



  • scales of justice schrieb:

    haben deine rep-befehle nur 16-bitter geschrieben und deine eigene loop gleich 32-bitter?

    das ganze läuft im 16-Bit Modus deswegen benutze ich ja auch stosw und nicht stosd und mein loop ist auch 16-Bit

    Im "16Bit-Modus" bist du nicht gezwungen, nur 16Bit-Befehle zu verwenden.
    Deine Begruendung fuer die Verwendung von stosw anstelle von stosd ist so nicht stichhaltig.
    Was meinst du mit "mein loop ist auch 16-Bit"?

    scales of justice schrieb:

    ich hab das Ganz aber ungefähr so wie in Nekus Beispiel gemacht,
    vielleicht ist die rep Möglichkeit einfach langsamer

    Nicht ganz nachvollziehbar. Der zentrale Punkt in Nekus Code ist naemlich genau ein "rep stosd". 😉



  • Im "16Bit-Modus" bist du nicht gezwungen, nur 16Bit-Befehle zu verwenden

    natürlich nicht, aber ich benutze nur 16-Bit, um die Abwärtskompatibilität zu wahren

    Deine Begruendung fuer die Verwendung von stosw anstelle von stosd ist so nicht stichhaltig

    stosd gibt es auf alten Prozessoren so weit ich weis auch noch nicht
    und wenn doch müsste es eigentlich langsamer sein, als zweimal stosw zu verwenden

    Was meinst du mit "mein loop ist auch 16-Bit"?

    einfach dass ich für den auch 16-Bit Code und keinen 32-Bit Code verwendet habe,
    dann wäre es klar warum loop schneller ist



  • am schnellsten geht's wenn man fpu und cpu parallel schreiben laesst:

    fillloop:
      mov dword ptr [edi+ecx],eax
      mov dword ptr [edi+ecx+4],edx
      fst qword ptr [edi+ecx+8]
      add ecx,16
      jnz fillloop
    

    mit:
    eax = 0 (oder womit auch immer man fuellen will)
    edx = 0
    st(0) = 0.0
    ecx = -blocksize (muss durch 16 teilbar sein)
    edi = bufferstart + blocksize (sollte 16-byte alligned sein)



  • mit mmx geht das am schnellsten, dazu gibt es paper bei amd. fix ist ebenfalls mit doubles und der fpu zu arbeiten, die kann dann auch 64bit am stück füllen.

    fpu und "cpu" gleichzeitig arbeiten zu lassen dürfte nicht sooo viel bringen, das bottleneck ist nicht die cpu, sondern das ram... aber alles nötige steht im paper irgendwo auf der amd seite.


  • Mod

    "am schnellsten" ist sowieso relativ 😉
    jedenfalls wird die antwort immer zumindest vom zielprozessor abhängen. und wenn das programm am ende auf einem 286er laufen soll und dort schnell genug ist, ist es doch sowieso egal, ob die gewählte variante auf einem athlon o.ä. suboptimal ist. rep stosx ist für pentium+ traditionell die schnellste variante große blöcke (mehrere cachezeilen) zu füllen - der befehl ist nachhaltig in dieser hinsichtig optimiert worden. für kleine blöcke gibt es verschiedene andere möglichkeiten. im übrigen sollte stets die größtmögliche wortbreite benutzt werden, selbst wenn der ergebende zugriff dann unausgerichtet wäre, ist das günstiger. und für alles <=486 ist die frage ohnehin absolut zu beantworten - sieht man von bestimmten zusätzlichen parametern ab, ist die ausführungszeit für diese prozessoren einfach und exakt im voraus zu bestimmen. im übrigen sei auf die software optimierungshandbücher von amd und intel verwiesen:
    http://www.amd.com/us-en/assets/content_type/white_papers_and_tech_docs/25112.PDF
    http://www.intel.com/design/pentium4/manuals/248966.htm


Anmelden zum Antworten