1. Dashboard
  2. Mitglieder
    1. Letzte Aktivitäten
    2. Benutzer online
    3. Team
    4. Mitgliedersuche
  3. Forenregeln
  4. Forum
    1. Unerledigte Themen
  • Anmelden
  • Registrieren
  • Suche
Alles
  • Alles
  • Artikel
  • Seiten
  • Forum
  • Erweiterte Suche
  1. AutoIt.de - Das deutschsprachige Forum.
  2. Mitglieder
  3. Oscar

Beiträge von Oscar

  • Fehler in der deutschen Hilfe bitte hier melden (Hilfedatei 3.3.14.2 2017.11.12)

    • Oscar
    • 1. Januar 2018 um 14:38

    Wenn Du schon WM_SIZE benutzt, kannst Du auch gleich die GUI-Elemente anhand der neuen Client-Coordinaten neu ausrichten.

    Das ist zwar mehr Programmieraufwand, aber man kann selbst genau bestimmen, welches Element in welcher Position bleibt bzw. verschoben wird:

    AutoIt
    #include <GUIConstants.au3>
    Global $iGuiW = 600, $iGuiH = 400
    Global $MainGUI = GUICreate("", $iGuiW, $iGuiH, -1, -1,  $WS_MINIMIZEBOX + $WS_MAXIMIZEBOX + $WS_SIZEBOX)
    Global $patternGroup = GUICtrlCreateGroup("", 10, 210, 580, 65)
    Global $g_idPattern = GUICtrlCreateInput("", 30, 230, 540, 30)
    GUISetState(@SW_SHOW)
    GUIRegisterMsg($WM_SIZE, "_WM_SIZE")
    
    While GUIGetMsg() <> -3
    WEnd
    
    Func _WM_SIZE($hWnd, $Msg, $wParam, $lParam)
        Local $iW = BitAND($lParam, 0x0000ffff) ; Low-Order-Word = new Client-Width
        Local $iH = BitShift(BitAND($lParam, 0xffff0000), 16) ; Hi-Order-Word = new Client-Height
        GUICtrlSetPos($patternGroup, Round(10 / $iGuiW * $iW), 210, Round(580 / $iGuiW * $iW), 65)
        GUICtrlSetPos($g_idPattern, Round(30 / $iGuiW * $iW), 230, Round(540 / $iGuiW * $iW), 30)
        Return $GUI_RUNDEFMSG
    EndFunc
    Alles anzeigen
  • Jahreswechsel 2017 -> 2018

    • Oscar
    • 1. Januar 2018 um 11:32

    Ich wünsche euch auch ein frohes (und hoffentlich friedliches) neues Jahr! :part:

  • Control-Positionierung/Skalierung bei resizable GUIs

    • Oscar
    • 30. Dezember 2017 um 19:22

    Fenster größenveränderlich zu machen ist nicht einfach. Vor allem, wenn es sehr viele GUI-Elemente gibt.

    Mein Ansatz ist meist, dass ich eine Mindestgröße vorgebe und mit dieser Mindestgröße erstelle ich die GUI. Mit Hilfe von $GUI_DOCKHCENTER kann man die Elemente dann schonmal ganz gut ausrichten.

    Zitat von fakeraol

    Daß der Abstand eines, in einem Group-Control plazierten Input zu dessen Rändern sich beim Resizen verändert, kannst Du rein mit GUICtrlSetResizing nicht korrigieren. Dazu braucht es immer zusätzlichen Aufwand,

    Ich weiß nicht, ob ich das Problem richtig verstehe, aber so würde es schonmal ganz gut aussehen:

    AutoIt
    #include <GUIConstantsEx.au3>
    #include <WindowsConstants.au3>
    
    Opt('GUIResizeMode', $GUI_DOCKHCENTER + $GUI_DOCKTOP + $GUI_DOCKHEIGHT)
    $hGui = GUICreate('Resizing-Test', 580, 100, -1, -1, BitOR($GUI_SS_DEFAULT_GUI, $WS_SIZEBOX, $WS_MAXIMIZEBOX))
    GUISetBkColor(0xCCCCCC)
    
    GUICtrlCreateGroup('Gruppe1', 20, 20, 240, 60)
    $idInput1 = GUICtrlCreateInput('', 30, 40, 220, 25)
    GUICtrlCreateGroup('', -99, -99, 1, 1)
    
    GUICtrlCreateGroup('Gruppe2', 320, 20, 240, 60)
    $idInput2 = GUICtrlCreateInput('', 330, 40, 220, 25)
    GUICtrlCreateGroup('', -99, -99, 1, 1)
    GUISetState()
    Global $aWinPos = WinGetPos($hGui)
    GUIRegisterMsg($WM_GETMINMAXINFO, "MY_WM_GETMINMAXINFO")
    
    Do
    Until GUIGetMsg() = -3
    
    Func MY_WM_GETMINMAXINFO($hWnd, $msg, $wParam, $lParam)
        If $hWnd = $hGui Then
            Local $minmaxinfo = DllStructCreate("int;int;int;int;int;int;int;int;int;int", $lParam)
            DllStructSetData($minmaxinfo, 7, $aWinPos[2]) ; min X
            DllStructSetData($minmaxinfo, 8, $aWinPos[3]) ; min Y
            DllStructSetData($minmaxinfo, 9, @DesktopWidth) ; max X
            DllStructSetData($minmaxinfo, 10, @DesktopHeight) ; max Y
        EndIf
    EndFunc   ;==>MY_WM_GETMINMAXINFO
    Alles anzeigen
  • StringCompare in ASM

    • Oscar
    • 29. Dezember 2017 um 16:05

    Das heißt, wenn ich SSE verwenden will, dann muss der Anfang des Speicherbereichs 16 Byte-aligned sein und er muss auch in der Größe immer durch 16 teilbar sein?

    Für einen String, der beispielsweise 33 Byte lang ist, muss ich also 48 Bytes reservieren und den dann auch noch auf eine 16-Byte-Adresse ausrichten?

    Und wie sieht das mit der Übergabe bei AutoIt aus? Zum Beispiel bei der obigen Stringübergabe. Die Adresse scheint 16-Byte-aligned zu sein, aber ist auch die Größe korrekt?

  • StringCompare in ASM

    • Oscar
    • 29. Dezember 2017 um 11:04

    Wie ist das eigentlich bei der Optimierung mit SSE und gleich mehrere Bytes einlesen?

    Wenn man das Ende des Strings nicht kennt, dann liest man doch in einem "fremden", unter Umständen geschützten, Speicherbereich. Verursacht das nicht einen Absturz?

  • StringCompare in ASM

    • Oscar
    • 28. Dezember 2017 um 18:21
    Zitat von Andy

    Zu beachten ist, dass AutoIt bei Verwendung von Pointern auf Variablen immer (!) eine Kopie anlegt, mit dessen Pointer dann gearbeitet wird. Dann reduziert sich das Script zu

    Oh, ok!

    Das vereinfacht zumindest die Verwendung von kurzen Strings.

    Zitat von Andy

    Btw. ist die Programmierung der "schnellsten" Stringlen-Funktion eine Aufgabe für ASM-Programmierer seit anbeginn aller Zeiten...und ein guter Einstieg in die Verwendung von SSE...16Bytes auf einen Schlag vergleichen FTW

    Naja, ab welcher Stringlänge macht sich eine solche Optimierung denn bemerkbar?

    Meine obige Funktion habe ich übrigens auch noch etwas optimiert:

    AutoIt
        StringLen:                                    ; eax = Pointer auf den String
            mov ecx,-1                                ; ecx = Counter (auf -1 setzen)
            @CountLen:                                ; Schleife
                inc ecx                               ; ecx++
                test byte[eax+ecx],0xff               ; wenn "Zeichen and 0xff" <> 0
                jnz @CountLen                         ; dann Schleife fortsetzen
            inc ecx                                   ; ecx++ (Nullbyte mitzaehlen)
            ret                                       ; return = ecx = Anzahl der Zeichen
  • StringCompare in ASM

    • Oscar
    • 27. Dezember 2017 um 15:46

    Meine Assembler-Erfahrungen gehen weiter. Jetzt habe ich mir mal den Umgang mit Strings vorgenommen.

    Wie übergibt man Strings von AutoIt an Assembler?

    Und wie kann man in Assembler zwei Strings miteinander vergleichen?

    So nebenbei ist dann noch eine Assemblerfunktion zum ermitteln der Stringlänge entstanden.

    AutoIt
    #AutoIt3Wrapper_UseX64=n                          ; 32Bit-Modus
    #include "AssembleIt2\assembleit2_64.au3"         ; <- Achtung! Pfad evtl. anpassen!
    
    #Region ASM-Code
    #cs StringCompare
        Use32                                         ; 32Bit Modus!
        mov esi,[esp+4]                               ; esi = String1 (Pointer)
        mov edi,[esp+8]                               ; edi = String2 (Pointer)
        mov eax,esi                                   ; eax = esi (fuer StringLen)
        call StringLen                                ; die Laenge von String1 ermitteln (Return = ecx = Anzahl der Zeichen)
        mov ebx,ecx                                   ; und in ebx merken
        mov eax,edi                                   ; eax = edi (fuer StringLen)
        call StringLen                                ; die Laenge von String2 ermitteln (Return = ecx = Anzahl der Zeichen)
        cmp ecx,ebx                                   ; die beiden Laengen vergleichen
        jae @f                                        ; wenn String2 laenger oder gleich lang, dann ueberspringen
            mov ecx,ebx                               ; ansonsten Stringlaenge von String1 nach ecx
        @@:
    ;~     _ASMDBG_(); debug-gui anzeigen
        cld                                           ; Direction-Flag loeschen
        repe cmpsb                                    ; die Strings vergleichen (repe = wiederhole so viele Zeichen, wie in ecx vorgegeben)
        jb @below                                     ; wenn kleiner, dann springe zu @below
        ja @above                                     ; wenn groesser, dann springe zu @above
                                                      ; ansonsten
            mov eax,1                                 ; wenn String1 = String2, dann eax = 1
            ret                                       ; zurueck zu AutoIt
        @above:
            mov eax,2                                 ; wenn String1 > String2, dann eax = 2
            ret                                       ; zurueck zu AutoIt
        @below:
            mov eax,0                                 ; wenn String1 < String2, dann eax = 0
            ret                                       ; zurueck zu AutoIt
    
        StringLen:
            xor ecx,ecx                               ; ecx = Counter (auf 0 setzen)
            @CountLen:                                ; Schleife
                cmp byte[eax+ecx],0                   ; wenn das Nullbyte erreicht wurde
                je @CountEnd                          ; dann Schleife beenden
                inc ecx                               ; ansonsten ecx++
                jnz @CountLen                         ; wenn ungleich 0, dann @CountLen
                ret                                   ; wenn kein 0-Byte vorhanden, dann return (ecx = 0)
            @CountEnd:
            inc ecx                                   ; ecx++ (Nullbyte mitzaehlen)
            ret                                       ; return = ecx = Anzahl der Zeichen
    #ce
    #EndRegion ASM-Code
    
    Global Const $aCompare[] = ['<', '=', '>']
    Global $aString[2] = ['Das ist ein Text.1', 'Das ist ein Text.2']
    $tStruct1 = _String2Struct($aString[0])
    $pStruct1 = DllStructGetPtr($tStruct1)
    
    $tStruct2 = _String2Struct($aString[1])
    $pStruct2 = DllStructGetPtr($tStruct2)
    
    ;~ $sString1 = DllStructGetData($tStruct1, 1)
    ;~ ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') : $sString1 = ' & $sString1 & @CRLF & '>Error code: ' & @error & @CRLF) ;### Debug Console
    ;~ $sString2 = DllStructGetData($tStruct2, 1)
    ;~ ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') : $sString2 = ' & $sString2 & @CRLF & '>Error code: ' & @error & @CRLF) ;### Debug Console
    
    $ret = _AssembleIt2("dword", "StringCompare", "ptr", $pStruct1, "ptr", $pStruct2)
    ConsoleWrite(StringFormat('StringCompare: String1 %s String2\n', $aCompare[$ret]))
    
    $ret = _AssembleIt2("dword", "StringCompare", "ptr", $pStruct2, "ptr", $pStruct1)
    ConsoleWrite(StringFormat('StringCompare: String2 %s String1\n', $aCompare[$ret]))
    
    Func _String2Struct($sString)
        $sString &= Chr(0)
        Local $tStruct = DllStructCreate('char[' & StringLen($sString) & ']')
        DllStructSetData($tStruct, 1, $sString)
        Return $tStruct
    EndFunc
    Alles anzeigen

    Dateien

    StringCompare.au3 3,98 kB – 287 Downloads
  • Quicksort mit 32-Bit Integerwerten

    • Oscar
    • 26. Dezember 2017 um 08:49
    Zitat von Andy

    Oscar, TOP ASM-Code, handoptimiert und mit Multithreading den letzten Schliff gegeben.

    Bin mal gespannt wo das endet....

    Danke!

    Wobei ich an dieser Stelle Dir auch nochmal für Deine "assembleit2_64.au3"-UDF danken möchte.

    Die macht das einbinden von ASM in AutoIt doch erheblich einfacher. :klatschen:

    Leider ist die Lernkurve bei Assembler ja recht steil. Im Gegensatz zu AutoIt findet man recht wenig passende Dokus.

    Aber vielleicht kannst Du mir weiterhelfen:

    Die XMM-Register sind ja 128 Bit breit. Ich kann also 128 Bit (4 DWORDs) in einem Rutsch aus dem Speicher lesen.

    Aber wie komme ich an die einzelnen DWORDs ran?

    Wie bekomme ich die in ein 32-Bit-Register z.B. nach EAX?

  • Quicksort mit 32-Bit Integerwerten

    • Oscar
    • 25. Dezember 2017 um 19:25
    Zitat von AspirinJunkie

    Wenn der Mehraufwand hierfür zu groß wird kannst du auch versuchen das ganze größenabhängig zu machen:

    Bei großen Elementmengen den Median of three, bei kleinen das mittlere/zufällige Element, und bei kleinen Elementmengen Insertion-Sort.

    Oh Mist, ich habe einen Fehler bei der Umsetzung von "Median of three" (Post#12) gemacht. Statt ECX habe ich EDX geschrieben. Dadurch war der letzte Swap falsch und verursachte längere Laufzeiten.

    Und das führte auch zu der sehr langen Laufzeit (> 100 ms) bei bereits sortierten Zahlen. :whistling:

    Nachdem ich den Fehler behoben habe, beträgt die Laufzeit bei sortierten Zahlen jetzt nur noch ca. 3 ms.

    Ich verwende jetzt auch immer den "Median of three". Auch für die Slaves. So schwankt die Laufzeit zwischen 30 ms und 45 ms für 1 Mio DWORDs.

    Und Insertionsort für kleine (< 45) Elementmengen verwende ich doch bereits.

    Hier die korrekte Version:

    AutoIt
    #AutoIt3Wrapper_UseX64=n                          ; 32Bit-Modus
    #include <Memory.au3>
    #include <Array.au3>
    #include <Timers.au3>
    #include "assembleit2_64.au3"
    
    #Region ASM-Code
    #cs ASM_Sort
        Use32                                            ; 32Bit Modus!
        mov edi,dword[esp+4]                             ; edi = Pointer auf die Paramstruct
        mov esi,dword[edi]                               ; esi = Pointer auf die Datenstruct
        mov edx,dword[edi+4]                             ; edx = right = Anzahl der Daten
        dec edx                                          ; um eins verringern, weil die Datenstruct bei 0 beginnt
        mov eax,dword[edi+8]                             ; eax = ThreadID
        movd xmm2,eax                                    ; xmm2 = ThreadID sichern
        xor ecx,ecx                                      ; ecx = left (auf 0 setzen)
        push edx ecx                                     ; right und left auf den Stack (fuer Quicksort)
        call quicksort                                   ; Quicksort aufrufen (die Datenstruct sortieren)
        ret                                              ; Daten sortiert (Funktion beenden)
    
        quicksort:                                       ; die Quick-/Insertionsort-Funktion
            mov ecx,dword[esp+4]                         ; ecx = left
            mov edx,dword[esp+8]                         ; edx = right
            cmp ecx,edx                                  ; left und right vergleichen
            jae @end                                     ; wenn left >= right, dann Funktion beenden
                mov eax,edx
                sub eax,ecx                              ; right - left
                cmp eax,45                               ; mehr als 45 Elemente?
                jg @quick                                ; wenn ja, dann Quicksort
                    mov edi,ecx                          ; Nein, dann Insertionsort
                    inc edi                              ; edi = $i = left + 1
                    @fori:
                        mov ebx,dword[esi+edi*4]         ; ebx = Data[Insert]
                        mov eax,edi                      ; eax = $j = Insertpos
                        @forj:                           ; Einfuegeschleife
                            cmp eax,ecx                  ; Anfang erreicht?
                            jbe @break                   ; Ja, dann @break
                            cmp dword[esi-4+eax*4],ebx   ; Data[j-1] < Data[Insert]
                            jbe @break                   ; Ja, dann @break
                            movd xmm0,dword[esi-4+eax*4] ; Data[j-1] holen
                            movd dword[esi+eax*4],xmm0   ; als Data[j] speichern
                            dec eax                      ; j--
                            jmp @forj                    ; forj fortsetzen
                        @break:
                            mov dword[esi+eax*4],ebx     ; Data[Insert] nach Data[j] speichern
                            inc edi                      ; i++
                            cmp edi,edx                  ; i > right
                            jbe @fori                    ; Nein, dann @fori
                    ret 8                                ; Insertionsort beendet, Funktion verlassen
                @quick:                                  ; hier beginnt der Quicksort-Bereich
                push edx ecx                             ; edx und ecx auf den Stack (fuer Partition)
                call partition                           ; Partition aufrufen Return = ebx (Splitposition)
                pop ecx edx                              ; ecx und edx wiederherstellen
                movd eax,xmm2                            ; ThreadID wiederherstellen
                cmp eax,0                                ; ThreadID = 0 (Master-Thread)?
                jnz @f                                   ; Nein, dann ueberspringen
                    mov eax,ebx                          ; Ja, dann eax = ebx (Splitposition)
                    ret 8                                ; Funktion verlassen (eax = Rueckgabe an AutoIt)
                @@:
                push ebx edx                             ; Register sichern
                push ebx ecx                             ; ebx und ecx auf den Stack (right und left fuer Quicksort)
                call quicksort                           ; Quicksort aufrufen (rekursiv)
                pop edx ebx                              ; Register wiederherstellen
                inc ebx                                  ; ebx++
                push edx ebx                             ; edx und ebx auf den Stack (right und left fuer Quicksort)
                call quicksort                           ; Quicksort aufrufen (rekursiv)
            @end:
            ret 8                                        ; Quicksort-Funktion beendet (2 DWORDs = 8 Byte vom Stack loeschen)
    
        partition:
            mov ecx,dword[esp+4]                         ; ecx = left
            mov edx,dword[esp+8]                         ; edx = right
            ; Anfang "Median of three" (left, middle, right)
            mov eax,edx                                  ; eax = right
            sub eax,ecx                                  ; eax -= left
            shr eax,1                                    ; eax geteilt durch 2
            add eax,ecx                                  ; eax += left = mid
            mov edi,dword[esi+edx*4]                     ; edi = data[right]
            cmp edi,dword[esi+ecx*4]
            jae @f                                       ; data[right] >= data[left] dann ueberspringen
                movd xmm0,dword[esi+ecx*4]               ; xmm0 = data[left]
                mov dword[esi+ecx*4],edi                 ; swap data[left] <-> data[right]
                movd dword[esi+edx*4],xmm0
            @@:
            mov edi,dword[esi+eax*4]                     ; edi = data[mid]
            cmp edi,dword[esi+ecx*4]
            jae @f                                       ; data[mid] >= data[left] dann ueberspringen
                movd xmm0,dword[esi+ecx*4]               ; xmm0 = data[left]
                mov dword[esi+ecx*4],edi                 ; swap data[left] <-> data[mid]
                movd dword[esi+eax*4],xmm0
            @@:
            mov edi,dword[esi+edx*4]                     ; edi = data[right]
            cmp edi,dword[esi+eax*4]
            jae @f                                       ; data[right] >= data[mid] dann ueberspringen
                movd xmm0,dword[esi+eax*4]               ; xmm0 = data[mid]
                mov dword[esi+eax*4],edi                 ; swap data[mid] <-> data[right]
                movd dword[esi+edx*4],xmm0
            @@:
            movd xmm0,dword[esi+ecx*4]                   ; xmm0 = data[left]
            mov edi,dword[esi+eax*4]                     ; edi = data[mid]
            mov dword[esi+ecx*4],edi                     ; swap data[left] <-> data[mid]
            movd dword[esi+eax*4],xmm0
            ; edi = data[left] = Pivotwert (durch "Median of three" nach left getauscht)
            ; Ende "Median of three"
            mov eax,ecx                                  ; eax = left
            dec eax                                      ; eax-- (left-1)
            mov ebx,edx                                  ; ebx = right
            inc ebx                                      ; ebx++ (right+1)
            @loop:                                       ; Hauptschleife
                @left:                                   ; Schleife fuer die linke Seite
                    inc eax                              ; left++
                    cmp dword[esi+eax*4],edi             ; Vergleich Data[left] mit Pivotwert
                    jb @left                             ; wenn kleiner, dann Schleife @left
                @right:                                  ; Schleife fuer die rechte Seite
                    dec ebx                              ; right--
                    cmp dword[esi+ebx*4],edi             ; Vergleich Data[right] mit Pivotwert
                    ja @right                            ; wenn groesser, dann Schleife @right
                cmp eax,ebx                              ; Vergleich left und right
                jae @return                              ; wenn groesser/gleich, dann @return
                mov ecx,dword[esi+eax*4]                 ; Data[left] gegen Data[right] austauschen
                mov edx,dword[esi+ebx*4]
                mov dword[esi+eax*4],edx
                mov dword[esi+ebx*4],ecx
                jmp @loop                                ; und mit @loop fortfahren
            @return:
            ret
    #ce
    #EndRegion ASM-Code
    
    #region AssembleIt ; wenn diese 3 Zeilen aktiv sind, dann wird der obige ASM-Code in Binaercode umgewandelt
    ;~ $binarycode = _AssembleIt2('retbinary', 'ASM_Sort') ; gibt nur den assemblierten code zurück
    ;~ ConsoleWrite('$binarycode = "' & $binarycode & '"' & @CRLF)
    ;~ Exit
    #EndRegion AssembleIt
    
    #Region ASM-Binaercode ; $__g_bASMCode entspricht dem obigen ASM-Code im Binaerformat
    Global Const $__g_bASMCode = '0x8B7C24048B378B57044A8B4708660F6ED031C95251E801000000C38B4C24048B54240839D1735E89D029C883F82D7F2B89CF478B1CBE89F839C87614395C86FC760E660F6E4486FC660F7E048648EBE8891C864739D776DBC208005251E826000000595A660F7ED083F800750589D8C2080053525351E8A0FFFFFF5A5B435253E896FFFFFFC208008B4C24048B54240889D029C8D1E801C88B3C963B3C8E730D660F6E048E893C8E660F7E04968B3C863B3C8E730D660F6E048E893C8E660F7E04868B3C963B3C86730D660F6E0486893C86660F7E0496660F6E048E8B3C86893C8E660F7E048689C84889D34340393C8672FA4B393C9E77FA39D8730E8B0C868B149E891486890C9EEBE2C3'
    Global Const $__g_iMemSize = StringLen($__g_bASMCode) / 2 - 1 ; Codelaenge ermitteln
    Global Const $__g_pMem = _MemVirtualAlloc(0, $__g_iMemSize, $MEM_COMMIT, $PAGE_EXECUTE_READWRITE) ; Virtuellen Speicher reservieren
    If $__g_pMem = 0 Then Exit MsgBox(16, 'Error!', "Can't allocate virtual memory!")
    Global $__g_tASMCode = DllStructCreate('byte[' & $__g_iMemSize & ']', $__g_pMem) ; Structur fuer den Binaercode erstellen
    DllStructSetData($__g_tASMCode, 1, $__g_bASMCode) ; den Binaercode in die Structur schreiben
    Global $__g_pASMCode = DllStructGetPtr($__g_tASMCode) ; den Pointer der Structur holen
    ConsoleWrite(StringFormat('ASM-Code-Size:\t%i Bytes\n', $__g_iMemSize))
    #EndRegion ASM-Binaercode
    
    #Region Test-Vorbereitungen
    Global $iCount = 1000000, $iTimer, $ret ; $iCount = Anzahl der Array-Elemente
    ConsoleWrite(StringFormat('Test-Struct:\t%i Elemente\n', $iCount))
    #EndRegion Test-Vorbereitungen
    
    #Region Zufalls-Struct erstellen
    $iTimer = _Timer_Init()
    Global $tData = DllStructCreate('dword[' & $iCount & ']')
    Global $pData = DllStructGetPtr($tData)
    For $i = 0 To $iCount - 1
        DllStructSetData($tData, 1, Random(0, 2^31-1, 1), $i + 1)
    ;~     DllStructSetData($tData, 1, $i + 1, $i + 1)
    Next
    ConsoleWrite(StringFormat('StructCreate:\t%.3f ms\n\n', Round(_Timer_Diff($iTimer), 3)))
    #EndRegion Zufalls-Struct erstellen
    
    #Region Master-Thread
    ; der Master-Thread nimmt die erste Partitionierung vor und die beiden Partitionen
    ; werden dann von den beiden Einzelthreads parallel sortiert
    $iTimer = _Timer_Init()
    Global $iThreads = 3, $atParam[$iThreads], $apParams[$iThreads], $iSplit, $ahThread[2]
    For $i = 0 To $iThreads - 1
        $atParam[$i] = DllStructCreate('ptr data;dword count;dword threadid')
        $apParams[$i] = DllStructGetPtr($atParam[$i])
    Next
    
    DllStructSetData($atParam[0], 'data', $pData)
    DllStructSetData($atParam[0], 'count', $iCount)
    DllStructSetData($atParam[0], 'threadid', 0)
    $ret = DllCallAddress('uint:cdecl', $__g_pASMCode, 'ptr', $apParams[0])
    $iSplit = $ret[0]
    #EndRegion Master-Thread
    
    #Region Multi-Thread
    ; die Uebergabeparameter in die Parameter-Struct fuer beide Threads eintragen
    DllStructSetData($atParam[1], 'data', $pData)
    DllStructSetData($atParam[1], 'count', $iSplit - 1)
    DllStructSetData($atParam[1], 'threadid', 1)
    
    DllStructSetData($atParam[2], 'data', $pData + ($iSplit + 1) * 4)
    DllStructSetData($atParam[2], 'count', $iCount - $iSplit - 1)
    DllStructSetData($atParam[2], 'threadid', 1)
    
    $ret = DllCall("kernel32.dll", "hwnd", "CreateThread", "ptr", 0, "dword", 0, "long", $__g_pASMCode, "ptr", $apParams[1], "long", 0, "int*", 0)
    $ahThread[0] = $ret[0]
    
    $ret = DllCall("kernel32.dll", "hwnd", "CreateThread", "ptr", 0, "dword", 0, "long", $__g_pASMCode, "ptr", $apParams[2], "long", 0, "int*", 0)
    $ahThread[1] = $ret[0]
    
    Global $iExit
    Do
        $iExit = 0
        For $i = 0 To UBound($ahThread) - 1
            $ret = DllCall("Kernel32.dll", "uint64", "GetExitCodeThread", "ptr", $ahThread[$i], "dword*", 0)
            If $ret[2] <> 259 Then $iExit += 1
        Next
    Until $iExit = UBound($ahThread)
    ConsoleWrite(StringFormat('ASM_Sort:\t%.3f ms\n', Round(_Timer_Diff($iTimer), 3)))
    ConsoleWrite(StringFormat('Split-Element:\t%.i\n\n', $iSplit))
    #EndRegion Multi-Thread
    
    _MemVirtualFree($__g_pMem, $__g_iMemSize, $MEM_DECOMMIT)
    Alles anzeigen

    Dateien

    test_MT.au3 11,76 kB – 493 Downloads
  • Quicksort mit 32-Bit Integerwerten

    • Oscar
    • 23. Dezember 2017 um 20:09
    Zitat von AspirinJunkie

    in dem du für das Pivot-Element das erste mittlerste und letzte Element nimmst und hiervon den Median bildest.

    Ja, damit sinkt die Worst-Case-Wahrscheinlichkeit und im Mittel sieht das Ergebnis schon viel besser aus.

    Allerdings habe ich das "Median of three" nur für die Partitionierung des Master-Threads verwendet. Wenn ich das bei jeder Partitionierung verwende, dann frisst die Laufzeit zum ermitteln des Median den Vorteil auf.

    Vor allem bei bereits sortierten Daten steigt damit die Laufzeit auf das Doppelte an.

    Master benutzt "Median of three" und die Slaves den mittleren Pivotwert. Damit liegt die Laufzeit meistens bei 30...40 ms.

    Zitat von Xorianator

    Quickselect wäre doch eine Möglichkeit, wenn man es mit dem approximierten Median of medians verwendet.

    Damit würde die Hauptaufgabe auf den Master-Thread liegen und ich würde mit den Slaves keinen Geschwindigkeitsgewinn mehr erzielen.

    Die Suche nach einem brauchbaren Median muss schon sehr schnell gehen (Laufzeitmäßig).

    Ich werde mal noch den Dual-Pivot-Quicksort ausprobieren. Dann könnte ich auch drei Threads gleichzeitig starten. Mal sehen, wie es damit aussieht.

    Hier mal die "Median of three"-Variante:

    > Diese hier war falsch! Die korrekte Version gibt es in Post#15

  • Random in ASM

    • Oscar
    • 22. Dezember 2017 um 19:47

    Zur Zeit bin ich ja gerade auf dem Assembler-Trip (Andy hat mit "assembleit2_64.au3" dafür eine tolle UDF abgeliefert).

    Die Möglichkeit AutoIt mit Assembler-Funktionen zu beschleunigen, ist schon verlockend, aber man muss natürlich auch erstmal Erfahrungen sammeln.

    Kleine Funktionen einfach mal in Assembler umzusetzen helfen dabei.

    Und so ist dann eine Randomfunktion entstanden. Dabei habe ich bei einem Beispiel von Andy abgeguckt, aber ich hab's verstanden. :)

    AutoIt
    #AutoIt3Wrapper_UseX64=n                          ; 32Bit-Modus
    #include "assembleit2_64.au3"
    
    #Region ASM-Code
    #cs random
        Use32                                         ; 32Bit Modus!
        mov ecx,[esp+4]                               ; erster Parameter = "von"
        mov edi,[esp+8]                               ; zweiter Parameter = "bis"
        cmp ecx,edi                                   ; Ist "von" <= "bis"?
        jbe @f                                        ; Ja, dann ueberspringen
            mov eax,0xffffffff                        ; ansonsten Fehlerwert laden
            ret                                       ; und zu AutoIt zurueck
        @@:
        inc edi                                       ; edi++ (damit "bis" auch moeglich ist)
        sub edi,ecx                                   ; edi = "bis" minus "von" (= Bereich der Zufallszahlen)
        rdtsc                                         ; Time Stamp Counter (in EDX & EAX)
        rcl eax,cl                                    ; eax rotate left (Anzahl in CL)
        mov edx,123433                                ; Primzahl laden
        mul edx                                       ; eax damit multiplizieren
        xor edx,edx                                   ; edx = 0
        div edi                                       ; eax durch edi (Bereich) teilen (edx = Rest der Division)
        add edx,ecx                                   ; zu dem Rest der Division den "von"-Wert addieren (edx + ecx)
        mov eax,edx                                   ; edx nach eax kopieren
        ret                                           ; Funktion verlassen (eax = Rueckgabe an AutoIt)
    #ce
    #EndRegion ASM-Code
    $ret = _AssembleIt2("dword", "random", "dword", 1, "dword", 49)
    ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') : $ret = ' & $ret & @CRLF & '>Error code: ' & @error & @CRLF) ;### Debug Console
    Alles anzeigen
  • Quicksort mit 32-Bit Integerwerten

    • Oscar
    • 22. Dezember 2017 um 19:31
    Zitat von Andy

    Jetzt bin ich mal unverschämt und frage, wer bei der Verwendung von 1 Mio DWORDS als Integerwerte überhaupt mit AutoIt-"Arrays" arbeitet?!

    Eigentlich hast Du Recht!

    Wenn wir schon Assembler für die Geschwindigkeit einsetzen, dann kann man auch den Rest des AutoIt-Programms danach ausrichten.

    Diese SafeArray-UDF erscheint mir auch als ein recht großer "Moloch", den man dann mit rumschleppen muss.

  • Quicksort mit 32-Bit Integerwerten

    • Oscar
    • 22. Dezember 2017 um 19:23

    Ich habe meine Zeit jetzt erstmal in eine Multi-Thread-Version investiert.

    Eigentlich macht es ja wenig Sinn, gerade die ASM-Funktion weiter zu beschleunigen, wenn Array2Struct und Struct2Array ein Vielfaches der Zeit für die ASM-Funktion ausmachen.

    Aber ich wollte mal mit mehreren Threads arbeiten und die Sortierfunktion war gerade da. :D

    Da die Threads von AutoIt aus gestartet werden, muss ich ihnen also entsprechende Aufgaben zuweisen.

    Hier habe ich das jetzt so gemacht, dass zuerst ein Master-Thread gestartet wird, der die erste Partitionierung vom Quicksort übernimmt und dann die Splitposition zurück gibt (das geht recht schnell).

    Mit dieser Splitposition starte ich zwei Threads, die dann parallel die beiden Partitionen sortieren (das dauert dann länger, wenn man bei Millisekunden von länger sprechen kann). ;)

    Der Erfolg hängt aber sehr stark von der Splitposition ab. Befindet sich die Splitposition so ziemlich in der Mitte (bei 1Mio Elementen also irgendwo im 500tausender Bereich), dann verkürzt sich die Sortierzeit von ca. 56ms (Singlethread) auf ca. 30ms (Multithread):

    Code
    Split-Element:    513727
    ASM_Sort:    30.647 ms

    Und je schlechter die Splitposition wird:

    Code
    Split-Element:    724651
    ASM_Sort:    42.461 ms

    umso länger dauert das sortieren:

    Code
    Split-Element:    25823
    ASM_Sort:    55.198 ms

    Ich verwende ja das mittlere Array-Element als Pivotwert. Das ist bei bereits sortierten Daten super schnell:

    Code
    Split-Element:    499999
    ASM_Sort:    3.560 ms

    Das garantiert aber nicht, dass beide Partitionen bei Zufallsdaten auch annähernd gleich groß sind. Bei Zufallsdaten ist es schwierig einen geeigneten Pivotwert zu finden. :/

    Trotzdem war es schon ganz interessant, mal etwas Multithreading zu machen. :)

    AutoIt
    #AutoIt3Wrapper_UseX64=n                          ; 32Bit-Modus
    #include <Memory.au3>
    #include <Array.au3>
    #include "assembleit2_64.au3"
    
    #Region ASM-Code
    #cs ASM_Sort
        Use32                                            ; 32Bit Modus!
        mov edi,dword[esp+4]                             ; edi = Pointer auf die Paramstruct
        mov esi,dword[edi]                               ; esi = Pointer auf die Datenstruct
        mov edx,dword[edi+4]                             ; edx = right = Anzahl der Daten
        dec edx                                          ; um eins verringern, weil die Datenstruct bei 0 beginnt
        mov eax,dword[edi+8]                             ; eax = ThreadID
        movd xmm2,eax                                    ; xmm2 = ThreadID sichern
        xor ecx,ecx                                      ; ecx = left (auf 0 setzen)
        push edx ecx                                     ; right und left auf den Stack (fuer Quicksort)
        call quicksort                                   ; Quicksort aufrufen (die Datenstruct sortieren)
        ret                                              ; Daten sortiert (Funktion beenden)
    
        quicksort:                                       ; die Quick-/Insertionsort-Funktion
            mov ecx,dword[esp+4]                         ; ecx = left
            mov edx,dword[esp+8]                         ; edx = right
            cmp ecx,edx                                  ; left und right vergleichen
            jae @end                                     ; wenn left >= right, dann Funktion beenden
                mov eax,edx
                sub eax,ecx                              ; right - left
                cmp eax,45                               ; mehr als 45 Elemente?
                jg @quick                                ; wenn ja, dann Quicksort
                    mov edi,ecx                          ; Nein, dann Insertionsort
                    inc edi                              ; edi = $i = left + 1
                    @fori:
                        mov ebx,dword[esi+edi*4]         ; ebx = Data[Insert]
                        mov eax,edi                      ; eax = $j = Insertpos
                        @forj:                           ; Einfuegeschleife
                            cmp eax,ecx                  ; Anfang erreicht?
                            jbe @break                   ; Ja, dann @break
                            cmp dword[esi-4+eax*4],ebx   ; Data[j-1] < Data[Insert]
                            jbe @break                   ; Ja, dann @break
                            movd xmm0,dword[esi-4+eax*4] ; Data[j-1] holen
                            movd dword[esi+eax*4],xmm0   ; als Data[j] speichern
                            dec eax                      ; j--
                            jmp @forj                    ; forj fortsetzen
                        @break:
                            mov dword[esi+eax*4],ebx     ; Data[Insert] nach Data[j] speichern
                            inc edi                      ; i++
                            cmp edi,edx                  ; i > right
                            jbe @fori                    ; Nein, dann @fori
                    ret 8                                ; Insertionsort beendet, Funktion verlassen
                @quick:                                  ; hier beginnt der Quicksort-Bereich
                push edx ecx                             ; edx und ecx auf den Stack (fuer Partition)
                call partition                           ; Partition aufrufen Return = ebx (Splitposition)
                pop ecx edx                              ; ecx und edx wiederherstellen
                movd eax,xmm2                            ; ThreadID wiederherstellen
                cmp eax,0                                ; ThreadID = 0 (Master-Thread)?
                jnz @f                                   ; Nein, dann ueberspringen
                    mov eax,ebx                          ; Ja, dann eax = ebx (Splitposition)
                    ret 8                                ; Funktion verlassen (eax = Rueckgabe an AutoIt)
                @@:
                push ebx edx                             ; Register sichern
                push ebx ecx                             ; ebx und ecx auf den Stack (right und left fuer Quicksort)
                call quicksort                           ; Quicksort aufrufen (rekursiv)
                pop edx ebx                              ; Register wiederherstellen
                inc ebx                                  ; ebx++
                push edx ebx                             ; edx und ebx auf den Stack (right und left fuer Quicksort)
                call quicksort                           ; Quicksort aufrufen (rekursiv)
            @end:
            ret 8                                        ; Quicksort-Funktion beendet (2 DWORDs = 8 Byte vom Stack loeschen)
    
        partition:
            mov ecx,dword[esp+4]                         ; ecx = left
            mov edx,dword[esp+8]                         ; edx = right
            mov eax,edx                                  ; eax = edx (right)
            sub eax,ecx                                  ; eax = eax - ecx (right minus left)
            shr eax,1                                    ; eax shift right (geteilt durch 2)
            add eax,ecx                                  ; eax += ecx (= middle)
            movd xmm0,dword[esi+ecx*4]
            movd xmm1,dword[esi+eax*4]                   ; swap Data[left] <-> Data[middle]
            movd dword[esi+eax*4],xmm0
            movd dword[esi+ecx*4],xmm1
            movd edi,xmm1                                ; edi = Pivotwert = Data[left]
            mov eax,ecx
            dec eax                                      ; eax = left - 1
            mov ebx,edx
            inc ebx                                      ; ebx = right + 1
            @loop:                                       ; Hauptschleife
                @left:                                   ; Schleife fuer die linke Seite
                    inc eax                              ; left++
                    cmp dword[esi+eax*4],edi             ; Vergleich Data[left] mit Pivotwert
                    jb @left                             ; wenn kleiner, dann Schleife @left
                @right:                                  ; Schleife fuer die rechte Seite
                    dec ebx                              ; right--
                    cmp dword[esi+ebx*4],edi             ; Vergleich Data[right] mit Pivotwert
                    ja @right                            ; wenn groesser, dann Schleife @right
                cmp eax,ebx                              ; Vergleich left und right
                jae @return                              ; wenn groesser/gleich, dann @return
                mov ecx,dword[esi+eax*4]                 ; Data[left] gegen Data[right] austauschen
                mov edx,dword[esi+ebx*4]
                mov dword[esi+eax*4],edx
                mov dword[esi+ebx*4],ecx
                jmp @loop                                ; und mit @loop fortfahren
            @return:
            ret
    #ce
    #EndRegion ASM-Code
    
    #region AssembleIt ; wenn diese 3 Zeilen aktiv sind, dann wird der obige ASM-Code in Binaercode umgewandelt
    ;~ $binarycode = _AssembleIt2('retbinary', 'ASM_Sort') ; gibt nur den assemblierten code zurück
    ;~ ConsoleWrite('$binarycode = "' & $binarycode & '"' & @CRLF)
    ;~ Exit
    #EndRegion AssembleIt
    
    #Region ASM-Binaercode ; $__g_bASMCode entspricht dem obigen ASM-Code im Binaerformat
    Global Const $__g_bASMCode = '0x8B7C24048B378B57044A8B4708660F6ED031C95251E801000000C38B4C24048B54240839D1735E89D029C883F82D7F2B89CF478B1CBE89F839C87614395C86FC760E660F6E4486FC660F7E048648EBE8891C864739D776DBC208005251E826000000595A660F7ED083F800750589D8C2080053525351E8A0FFFFFF5A5B435253E896FFFFFFC208008B4C24048B54240889D029C8D1E801C8660F6E048E660F6E0C86660F7E0486660F7E0C8E660F7ECF89C84889D34340393C8672FA4B393C9E77FA39D8730E8B0C868B149E891486890C9EEBE2C3'
    Global Const $__g_iMemSize = StringLen($__g_bASMCode) / 2 - 1 ; Codelaenge ermitteln
    Global Const $__g_pMem = _MemVirtualAlloc(0, $__g_iMemSize, $MEM_COMMIT, $PAGE_EXECUTE_READWRITE) ; Virtuellen Speicher reservieren
    If $__g_pMem = 0 Then Exit MsgBox(16, 'Error!', "Can't allocate virtual memory!")
    Global $__g_tASMCode = DllStructCreate('byte[' & $__g_iMemSize & ']', $__g_pMem) ; Structur fuer den Binaercode erstellen
    DllStructSetData($__g_tASMCode, 1, $__g_bASMCode) ; den Binaercode in die Structur schreiben
    Global $__g_pASMCode = DllStructGetPtr($__g_tASMCode) ; den Pointer der Structur holen
    ConsoleWrite(StringFormat('ASM-Code-Size:\t%i Bytes\n', $__g_iMemSize))
    #EndRegion ASM-Binaercode
    
    #Region Test-Vorbereitungen
    Global $iCount = 1000000, $iTimer, $ret ; $iCount = Anzahl der Array-Elemente
    Global $aRanData[$iCount], $aData
    ConsoleWrite(StringFormat('Test-Array:\t%i Elemente\n', $iCount))
    #EndRegion Test-Vorbereitungen
    
    #Region Zufalls-Array erstellen
    $iTimer = TimerInit()
    For $i = 0 To $iCount - 1
    ;~     $aRanData[$i] = Random(0, 2^31-1, 1)
    ;~     $aRanData[$i] = 1 + $i ; aufwaerts sortiert
        $aRanData[$i] = $iCount - $i ; abwaerts sortiert
    Next
    ;~ $tmp = $aRanData[$iCount - 1] ; swap A[last] <-> A[0]
    ;~ $aRanData[$iCount - 1] = $aRanData[0]
    ;~ $aRanData[0] = $tmp
    ConsoleWrite(StringFormat('ArrayCreate:\t%.3f ms\n\n', Round(TimerDiff($iTimer), 3)))
    #EndRegion Zufalls-Array erstellen
    
    #Region Array2Struct
    $aData = $aRanData ; damit die Ausgangsbedingungen gleich sind
    $iTimer = TimerInit()
    Global $tData = DllStructCreate('dword[' & $iCount & ']')
    Global $pData = DllStructGetPtr($tData)
    For $i = 0 To $iCount - 1
        DllStructSetData($tData, 1, $aData[$i], $i + 1)
    Next
    ConsoleWrite(StringFormat('Array2Struct:\t%.3f ms\n', Round(TimerDiff($iTimer), 3)))
    #EndRegion Array2Struct
    
    #Region Master-Thread
    ; der Master-Thread nimmt die erste Partitionierung vor und die beiden Partitionen
    ; werden dann von den beiden Einzelthreads parallel sortiert
    $iTimer = TimerInit()
    Global $iThreads = 3, $atParam[$iThreads], $apParams[$iThreads], $iSplit, $ahThread[2]
    For $i = 0 To $iThreads - 1
        $atParam[$i] = DllStructCreate('ptr data;dword count;dword threadid')
        $apParams[$i] = DllStructGetPtr($atParam[$i])
    Next
    
    DllStructSetData($atParam[0], 'data', $pData)
    DllStructSetData($atParam[0], 'count', $iCount)
    DllStructSetData($atParam[0], 'threadid', 0)
    $ret = DllCallAddress('uint:cdecl', $__g_pASMCode, 'ptr', $apParams[0])
    $iSplit = $ret[0]
    ConsoleWrite(StringFormat('Split-Element:\t%.i\n', $iSplit))
    ;~ ConsoleWrite(StringFormat('Split-Thread:\t%.3f ms\n', Round(TimerDiff($iTimer), 3)))
    #EndRegion Master-Thread
    
    #Region Multi-Thread
    ; die Uebergabeparameter in die Parameter-Struct fuer beide Threads eintragen
    DllStructSetData($atParam[1], 'data', $pData)
    DllStructSetData($atParam[1], 'count', $iSplit - 1)
    DllStructSetData($atParam[1], 'threadid', 1)
    
    DllStructSetData($atParam[2], 'data', $pData + ($iSplit + 1) * 4)
    DllStructSetData($atParam[2], 'count', $iCount - $iSplit - 1)
    DllStructSetData($atParam[2], 'threadid', 1)
    
    $ret = DllCall("kernel32.dll", "hwnd", "CreateThread", "ptr", 0, "dword", 0, "long", $__g_pASMCode, "ptr", $apParams[1], "long", 0, "int*", 0)
    $ahThread[0] = $ret[0]
    
    $ret = DllCall("kernel32.dll", "hwnd", "CreateThread", "ptr", 0, "dword", 0, "long", $__g_pASMCode, "ptr", $apParams[2], "long", 0, "int*", 0)
    $ahThread[1] = $ret[0]
    
    Global $iExit
    Do
        $iExit = 0
        For $i = 0 To UBound($ahThread) - 1
            $ret = DllCall("Kernel32.dll", "uint64", "GetExitCodeThread", "ptr", $ahThread[$i], "dword*", 0)
            If $ret[2] <> 259 Then $iExit += 1
        Next
    Until $iExit = UBound($ahThread)
    ConsoleWrite(StringFormat('ASM_Sort:\t%.3f ms\n', Round(TimerDiff($iTimer), 3)))
    #EndRegion Multi-Thread
    
    #Region Struct2Array
    $iTimer = TimerInit()
    For $i = 0 To $iCount - 1
        $aData[$i] = DllStructGetData($tData, 1, $i + 1)
    Next
    ConsoleWrite(StringFormat('Struct2Array:\t%.3f ms\n\n', Round(TimerDiff($iTimer), 3)))
    #EndRegion Struct2Array
    
    ;~ _ArrayDisplay($aData)
    
    _MemVirtualFree($__g_pMem, $__g_iMemSize, $MEM_DECOMMIT)
    Alles anzeigen

    Dateien

    test_MT.au3 11,16 kB – 499 Downloads
  • Fensterinformationen via Linksklick entziehen

    • Oscar
    • 21. Dezember 2017 um 16:19

    Klar, wenn Du auf den Button klickst, bekommst Du das Handle des Buttons.

    Dann musst Du Dir eben mit _WinAPI_GetParent und dem Button-Handle das Fenster-Handle holen.

  • Quicksort mit 32-Bit Integerwerten

    • Oscar
    • 20. Dezember 2017 um 13:07

    Andy: Das erfordert dann aber einen zusätzlichen Speicher (bei 1Mio. DWORDs = 4 Mio. Bytes wären das zusätzliche 8 Mio. Bytes für die Chars). Aber das bestätigt eigentlich meine Vermutung, dass die vielen DLLStructGetData die eigentliche Bremse sind. Es fehlt in AutoIt, die Möglichkeit die Structur wieder schnell in ein Array umzuwandeln.

    Bitnugger: Ich bin noch am durchtesten, aber verstehe ich das richtig, dass dort quasi ein Pseudo-Variant-Array erzeugt und das dann zur Übergabe nach AutoIt benutzt wird. Kann ich das dann von beiden "Seiten" aus benutzen? Und was ist, wenn man mehrere Arrays benötigt?

    Wie werden da dann die Daten umkopiert?

  • GetUniqueColors

    • Oscar
    • 20. Dezember 2017 um 12:47

    Bei mir sieht das so aus:

    Code
    Anzahl zu sortierender 32-Bit-Farben = 33554432   = 0x0000000002000000
    
    verwendetes Device  2;1;2;CPU;Intel(R) Core(TM) i5-4690 CPU @ 3.50GHz;43175824;42773232
    @ Debug(539) : $MAX_WORK_GROUP_SIZE = 8192
    @@ Debug(547) : $LOCAL_WORKGROUP_SIZE = 8192 
    @@ Debug(107) : Kernelruntime = 4336ms
    @@ Debug(111) : Speicherkopierzeit = 15ms
    @@ Debug(112) : Anzahl Kernelaufrufe = 300
    
    verwendetes Device  1;1;4;GPU;GeForce GTX 1050 Ti;44249632;44248672
    @@ Debug(539) : $MAX_WORK_GROUP_SIZE = 1024
    @@ Debug(547) : $LOCAL_WORKGROUP_SIZE = 1024 
    @@ Debug(107) : Kernelruntime = 885ms
    @@ Debug(111) : Speicherkopierzeit = 10ms
    @@ Debug(112) : Anzahl Kernelaufrufe = 300
    Alles anzeigen

    Also das Ergebnis der GPU ist ja schon nicht schlecht, aber das Ergebnis von der CPU ist dann ja doch schlechter als mein ASM-Code zum sortieren.

  • Quicksort mit 32-Bit Integerwerten

    • Oscar
    • 19. Dezember 2017 um 15:34

    In GetUniqueColors gab es die Aufgabenstellung alle Farben in einem Bild zu zählen.

    Hier soll es jetzt um das sortieren von 32-Bit-Integerwerten gehen. Wenn man ein AutoIt-Array mit 1Mio Elementen hat und dieses mit _ArraySort sortieren will, dann dauert das ziemlich lange (ca. 26 Sekunden auf meinem Rechner und mit Zufallsdaten).

    Das Ganze dauert in Assembler ca. 56 Millisekunden. Zumindest das reine sortieren. Um in Assembler mit dem AutoIt-Array arbeiten zu können, müssen wir es in eine Struct kopieren und nach dem sortieren in Assembler müssen wir die Daten aus der Struct wieder in das Array zurück kopieren.

    Dieses umkopieren dauert jedes Mal ca. 1.2 Sekunden. Somit ergibt sich für das sortieren mit dem Assemblerprogramm eine Gesamtlaufzeit von ca. 2.5 Sekunden. Das ist aber immer noch ein mächtiger Geschwindigkeitsgewinn (nur ein Zehntel der Zeit).

    Dieses Assemblerprogramm möchte ich euch hier vorstellen.

    Dazu habe ich die Quicksort-Funktion aus dem obigen Thread noch verbessert:

    - Beim Start wird überprüft, ob die Daten bereits sortiert vorliegen. Das kostet zwar ein paar Millisekunden für den Test, aber es ist schneller als die sortierten Daten erneut durch die Funktion zu schicken.

    - Quicksort wird nicht ausschließlich verwendet. Bei weniger als 45 Elementen pro Partition werden die verbleibenden Elemente mit InsertionSort sortiert. Diese Kombination ist schneller, als ein reines Qiucksort.

    - Das Pivotelement ist jetzt nicht mehr das erste (linke) Element einer Partition, sondern das Mittlere. Das bringt einen enormem Geschwindigkeitsgewinn bei teilsortierten Listen.

    Ausgabe bei mir:

    Code
    ASM-Code-Size:    221 Bytes
    Test-Array:    1000000 Elemente
    ArrayCreate:    1237.211 ms
    
    Array2Struct:    1116.148 ms
    ASM_Sort:    55.360 ms
    Struct2Array:    1190.879 ms
    
    _ArraySort:    25923.010 ms

    Wer selbst am ASM-Code rumbasteln will, benötigt "Assembleit2_64" von Andy und muss die Zeilen 4 und 166 aktivieren und die Zeilen 167 und 168 auskommentieren.

    Ansonsten habe ich alles ausgiebig kommentiert, sodass der Ablauf verständlich wird.

    AutoIt
    #AutoIt3Wrapper_UseX64=n                          ; 32Bit-Modus
    #include <Memory.au3>
    #include <Array.au3>
    ;~ #include "assembleit2_64.au3"
    
    #Region ASM-Code
    #cs ASM_Sort
        Use32                                            ; 32Bit Modus!
        mov esi,dword[esp+4]                             ; esi = Pointer auf die Datenstruct
        mov edx,dword[esp+8]                             ; edx = right = Anzahl der Daten
        dec edx                                          ; um eins verringern, weil die Datenstruct bei 0 beginnt
    
        xor ecx,ecx                                      ; ecx (auf 0 setzen)
        @check1:                                         ; Schleife, zum testen, ob die Daten bereits sortiert sind (aufwaerts)
            mov eax,dword[esi+ecx*4]                     ; eax = Data[ecx]
            inc ecx                                      ; ecx++
            cmp ecx,edx                                  ; ecx > edx?
            ja @SortReturn                               ; Ja, dann alle Daten getestet, also bereits sortiert (Funktion beenden)
            cmp eax,dword[esi+ecx*4]
            jbe @check1                                  ; Wenn Data[ecx] <= Data[ecx+1], dann weiter mit @check1
        xor ecx,ecx                                      ; ecx (auf 0 setzen)
        @check2:                                         ; Schleife, zum testen, ob die Daten bereits sortiert sind (abwaerts)
            mov eax,dword[esi+ecx*4]                     ; eax = Data[ecx]
            inc ecx                                      ; ecx++
            cmp ecx,edx                                  ; ecx > edx?
            ja @SortReturn                               ; Ja, dann alle Daten getestet, also bereits sortiert (Funktion beenden)
            cmp eax,dword[esi+ecx*4]
            jae @check2                                  ; Wenn Data[ecx] >= Data[ecx+1], dann weiter mit @check2
    
        xor ecx,ecx                                      ; ecx = left (auf 0 setzen)
        push edx ecx                                     ; right und left auf den Stack (fuer Quicksort)
        call quicksort                                   ; Quicksort aufrufen (die Datenstruct sortieren)
    
        @SortReturn:
        ret                                              ; Daten sortiert (Funktion beenden)
    
        quicksort:                                       ; die Quick-/Insertionsort-Funktion
            mov ecx,dword[esp+4]                         ; ecx = left
            mov edx,dword[esp+8]                         ; edx = right
            cmp ecx,edx                                  ; left und right vergleichen
            jae @end                                     ; wenn left >= right, dann Funktion beenden
                mov eax,edx
                sub eax,ecx                              ; right - left
                cmp eax,45                               ; mehr als 45 Elemente?
                jg @quick                                ; wenn ja, dann Quicksort
                    mov edi,ecx                          ; Nein, dann Insertionsort
                    inc edi                              ; edi = $i = left + 1
                    @fori:
                        mov ebx,dword[esi+edi*4]         ; ebx = Data[Insert]
                        mov eax,edi                      ; eax = $j = Insertpos
                        @forj:                           ; Einfuegeschleife
                            cmp eax,ecx                  ; Anfang erreicht?
                            jbe @break                   ; Ja, dann @break
                            cmp dword[esi-4+eax*4],ebx   ; Data[j-1] < Data[Insert]
                            jbe @break                   ; Ja, dann @break
                            movd xmm0,dword[esi-4+eax*4] ; Data[j-1] holen
                            movd dword[esi+eax*4],xmm0   ; als Data[j] speichern
                            dec eax                      ; j--
                            jmp @forj                    ; forj fortsetzen
                        @break:
                            mov dword[esi+eax*4],ebx     ; Data[Insert] nach Data[j] speichern
                            inc edi                      ; i++
                            cmp edi,edx                  ; i > right
                            jbe @fori                    ; Nein, dann @fori
                    ret 8                                ; Insertionsort beendet, Funktion verlassen
                @quick:                                  ; hier beginnt der Quicksort-Bereich
                push edx ecx                             ; edx und ecx auf den Stack (fuer Partition)
                call partition                           ; Partition aufrufen (ebx = split)
                pop ecx edx                              ; ecx und edx wiederherstellen
                push ebx edx                             ; Register sichern
                push ebx ecx                             ; ebx und ecx auf den Stack (right und left fuer Quicksort)
                call quicksort                           ; Quicksort aufrufen (rekursiv)
                pop edx ebx                              ; Register wiederherstellen
                inc ebx                                  ; ebx++
                push edx ebx                             ; edx und ebx auf den Stack (right und left fuer Quicksort)
                call quicksort                           ; Quicksort aufrufen (rekursiv)
            @end:
            ret 8                                        ; Quicksort-Funktion beendet (2 DWORDs = 8 Byte vom Stack loeschen)
    
        partition:                                       ; Funktion zum partitionieren der Daten
            mov ecx,dword[esp+4]                         ; ecx = left
            mov edx,dword[esp+8]                         ; edx = right
            mov eax,edx                                  ; eax = edx (right)
            sub eax,ecx                                  ; eax = eax - ecx (right minus left)
            shr eax,1                                    ; eax shift right (geteilt durch 2)
            add eax,ecx                                  ; eax += ecx (= middle)
            movd xmm0,dword[esi+ecx*4]
            movd xmm1,dword[esi+eax*4]                   ; swap Data[left] <-> Data[middle]
            movd dword[esi+eax*4],xmm0
            movd dword[esi+ecx*4],xmm1
            movd edi,xmm1                                ; edi = Pivotwert = Data[left]
            mov eax,ecx
            dec eax                                      ; eax = left - 1
            mov ebx,edx
            inc ebx                                      ; ebx = right + 1
            @loop:                                       ; Hauptschleife
                @left:                                   ; Schleife fuer die linke Seite
                    inc eax                              ; left++
                    cmp dword[esi+eax*4],edi             ; Vergleich Data[left] mit Pivotwert
                    jb @left                             ; wenn kleiner, dann Schleife @left
                @right:                                  ; Schleife fuer die rechte Seite
                    dec ebx                              ; right--
                    cmp dword[esi+ebx*4],edi             ; Vergleich Data[right] mit Pivotwert
                    ja @right                            ; wenn groesser, dann Schleife @right
                cmp eax,ebx                              ; Vergleich left und right
                jae @return                              ; wenn groesser/gleich, dann @return
                mov ecx,dword[esi+eax*4]                 ; Data[left] gegen Data[right] austauschen
                mov edx,dword[esi+ebx*4]
                mov dword[esi+eax*4],edx
                mov dword[esi+ebx*4],ecx
                jmp @loop                                ; und mit @loop fortfahren
            @return:
            ret                                          ; right zurueckgeben (ebx)
    #ce
    #EndRegion ASM-Code
    
    #region AssembleIt ; wenn diese 3 Zeilen aktiv sind, dann wird der obige ASM-Code in Binaercode umgewandelt
    ;~ $binarycode = _AssembleIt2('retbinary', 'ASM_Sort') ; gibt nur den assemblierten code zurück
    ;~ ConsoleWrite('$binarycode = "' & $binarycode & '"' & @CRLF)
    ;~ Exit
    #EndRegion AssembleIt
    
    #Region ASM-Binaercode ; $__g_bASMCode entspricht dem obigen ASM-Code im Binaerformat
    Global Const $__g_bASMCode = '0x8B7424048B5424084A31C98B048E4139D1771D3B048E76F331C98B048E4139D1770E3B048E73F331C95251E801000000C38B4C24048B54240839D1735089D029C883F82D7F2B89CF478B1CBE89F839C87614395C86FC760E660F6E4486FC660F7E048648EBE8891C864739D776DBC208005251E818000000595A53525351E8AEFFFFFF5A5B435253E8A4FFFFFFC208008B4C24048B54240889D029C8D1E801C8660F6E048E660F6E0C86660F7E0486660F7E0C8E660F7ECF89C84889D34340393C8672FA4B393C9E77FA39D8730E8B0C868B149E891486890C9EEBE2C3'
    Global Const $__g_iMemSize = StringLen($__g_bASMCode) / 2 - 1 ; Codelaenge ermitteln
    Global Const $__g_pMem = _MemVirtualAlloc(0, $__g_iMemSize, $MEM_COMMIT, $PAGE_EXECUTE_READWRITE) ; Virtuellen Speicher reservieren
    If $__g_pMem = 0 Then Exit MsgBox(16, 'Error!', "Can't allocate virtual memory!")
    Global $__g_tASMCode = DllStructCreate('byte[' & $__g_iMemSize & ']', $__g_pMem) ; Structur fuer den Binaercode erstellen
    DllStructSetData($__g_tASMCode, 1, $__g_bASMCode) ; den Binaercode in die Structur schreiben
    Global $__g_pASMCode = DllStructGetPtr($__g_tASMCode) ; den Pointer der Structur holen
    ConsoleWrite(StringFormat('ASM-Code-Size:\t%i Bytes\n', $__g_iMemSize))
    #EndRegion ASM-Binaercode
    
    #Region Test-Vorbereitungen
    Global $iCount = 1000000, $iTimer, $ret ; $iCount = Anzahl der Array-Elemente
    Global $aRanData[$iCount], $aData
    ConsoleWrite(StringFormat('Test-Array:\t%i Elemente\n', $iCount))
    #EndRegion Test-Vorbereitungen
    
    #Region Zufalls-Array erstellen
    $iTimer = TimerInit()
    For $i = 0 To $iCount - 1
        $aRanData[$i] = Random(0, 2^31-1, 1)
    ;~     $aRanData[$i] = $i ; aufwaerts sortiert
    ;~     $aRanData[$i] = $iCount - $i ; abwaerts sortiert
    Next
    ;~ $tmp = $aRanData[$iCount - 1] ; swap A[last] <-> A[0]
    ;~ $aRanData[$iCount - 1] = $aRanData[0]
    ;~ $aRanData[0] = $tmp
    ConsoleWrite(StringFormat('ArrayCreate:\t%.3f ms\n\n', Round(TimerDiff($iTimer), 3)))
    #EndRegion Zufalls-Array erstellen
    
    #Region Array2Struct
    $aData = $aRanData ; damit die Ausgangsbedingungen gleich sind
    $iTimer = TimerInit()
    Global $tData = DllStructCreate('dword[' & $iCount & ']')
    Global $pData = DllStructGetPtr($tData)
    For $i = 0 To $iCount - 1
        DllStructSetData($tData, 1, $aData[$i], $i + 1)
    Next
    ConsoleWrite(StringFormat('Array2Struct:\t%.3f ms\n', Round(TimerDiff($iTimer), 3)))
    #EndRegion Array2Struct
    
    #Region ASM-Code aufrufen
    $iTimer = TimerInit()
    ;~ $ret = _AssembleIt2('dword', 'ASM_Sort', 'ptr', $pData, 'dword', $iCount)
    $ret = DllCallAddress('uint:cdecl', $__g_pASMCode, 'ptr', $pData, 'dword', $iCount)
    $ret = $ret[0]
    ConsoleWrite(StringFormat('ASM_Sort:\t%.3f ms\n', Round(TimerDiff($iTimer), 3)))
    #EndRegion ASM-Code aufrufen
    
    #Region Struct2Array
    $iTimer = TimerInit()
    For $i = 0 To $iCount - 1
        $aData[$i] = DllStructGetData($tData, 1, $i + 1)
    Next
    ConsoleWrite(StringFormat('Struct2Array:\t%.3f ms\n\n', Round(TimerDiff($iTimer), 3)))
    #EndRegion Struct2Array
    
    #Region AutoIt ArraySort
    $aData = $aRanData ; damit die Ausgangsbedingungen gleich sind
    $iTimer = TimerInit()
    _ArraySort($aData, 0, 0, 0, 0, 1) ; mit Dual-Pivot aufrufen (ist hier schneller)
    ConsoleWrite(StringFormat('_ArraySort:\t%.3f ms\n', Round(TimerDiff($iTimer), 3)))
    #EndRegion AutoIt ArraySort
    
    ;~ _ArrayDisplay($aData)
    
    _MemVirtualFree($__g_pMem, $__g_iMemSize, $MEM_DECOMMIT)
    Alles anzeigen

    Dateien

    _ASM_Sort.au3 10,63 kB – 557 Downloads
  • Fensterinformationen via Linksklick entziehen

    • Oscar
    • 18. Dezember 2017 um 15:07
    Zitat von Reiterfuchs

    Meine Frage ist an euch wie ich innerhalb dieser Funktionsstruktur (vermutlich innerhalb der jeweilen If-Abfrage) an die Fensterinformationen komme.


    Gibt es eine Möglichkeit an diese mittels _isPressed zu kommen?

    Schau Dir mal in der Hilfe das Beispiel zu "_WinAPI_WindowFromPoint" an. So bekommst Du das Window-Handle unter der Mausposition.

  • Meldetext mit automatischen Zeilenumbruch

    • Oscar
    • 18. Dezember 2017 um 15:00

    Wenn Du das mit der automatischen Textgröße nicht unbedingt brauchst, dann reicht auch eine einfache Variante:

    AutoIt
    #include <WindowsConstants.au3>
    Global $hGui = GUICreate('Meldung', 640, 480, -1, -1, BitOR($WS_POPUP, $WS_MAXIMIZE))
    GUISetBkColor(0xFF6060)
    $aSize = WinGetClientSize($hGui)
    Global $idText = GUICtrlCreateLabel('Ich soll einen Alarmmonitor für meine Freiwillige Feuerwehr aufbauen. Der Meldetext ist dabei immer sehr lang (mehr als 100 Zeichen). Jetzt suche ich ein Skript das den Text Bildschirmfüllend und mit automatischen Zeilenumbruch und automatischen anpassen der Textgröße. Kennt einer solch ein Skript bzw. einen Lösungsansatz?', 20, 20, $aSize[0] -40, $aSize[1] - 40)
    GUICtrlSetFont(-1, 76, 400, 0, 'Tahoma')
    GUICtrlSetBkColor(-1, 0xEEEEEE)
    GUISetState(@SW_SHOW)
    Do
    Until GUIGetMsg() = -3
  • GetUniqueColors

    • Oscar
    • 15. Dezember 2017 um 19:51
    Zitat von Mars

    So viel Elan für etwas das bereits auf 5 Arten erfolgreich gelöst wurde

    Vom Prinzip her ja, aber man kann das ja noch verbessern. ;)

    Und so nebenbei lerne ich noch, wie man AutoIt mit Assembler-Routinen verbessern kann.

    Zum Beispiel: Ein _ArraySort, mit einem Array das 1Mio Elemente (DWORDs) enthält, dauert auf meinem Rechner fast 40 Sekunden. Das gleiche Array mit Assembler sortiert dauert 60 Millisekunden. Selbst wenn man noch Array2Struct und Struct2Array (jeweils ca. 1.2s) mitrechnet, ist das mit unter 3 Sekunden noch sehr schnell.

Spenden

Jeder Euro hilft uns, Euch zu helfen.

Download

AutoIt Tutorial
AutoIt Buch
Onlinehilfe
AutoIt Entwickler
  1. Datenschutzerklärung
  2. Impressum
  3. Shoutbox-Archiv
Community-Software: WoltLab Suite™