Geschwindigkeitsoptimierung in AutoIt

    • Offizieller Beitrag

    Hey,

    Ich wollte hier mal ein kleines Tutorial schreiben, wie man sein Script, wenn es wirklich um jede nanosekunde geht, optimieren kann. Ich würde mir wünschen, das ihr auch was dazu Beiträgt, egal in welchem Bereiech (Sei es nun einfache Funktionen, GDI, GUI, Operatoren, Arrays, ...) - immer alles her damit. :)

    Unten findet ihr ein Script mit dem ihr es testen könnt und eure Ergebnisse teilen könnt:

    Ich mache mal den Anfang:

    PC Info
    Zitat

    >Result with AutoIt 3.3.12.0 x86.
    >CPU: Intel(R) Core(TM) i5-4590 CPU @ 3.30GHz X64
    >RAM: 4.97/7.94 GB
    >OS: WIN_81 (9600) X64

    If/Switch/Select/Ternär

    Bei den If/Switch/Select gibt es kaum ein unterschied mittlerweile wobei es bei If abhängig davon ist, ob die expression wahr oder falsch ist, bei Switcht uns Select allerdings nicht. Witzig, weil es früher andersrum war, da hat If Switch um längen geschlagen mittlerweile ist dem nicht mehr so.
    Die neuen Ternären Operatoren in der Aktuellen AutoIt Version sind allerdings die schnellsten ($vParam = True ? True : False)

    Zitat

    >Parameter: True
    !Func If x = True Then (1) needs for 1048575 runs 3866.03 ms (Average: 0.0037 ms) --> 1.1x
    -Func If x <> True Then (2) needs for 1048575 runs 3725.93 ms (Average: 0.0036 ms) --> 1.0x
    -Func Switch (3) needs for 1048575 runs 3702.88 ms (Average: 0.0035 ms) --> 1.0x
    -Func Select (4) needs for 1048575 runs 3737.54 ms (Average: 0.0036 ms) --> 1.0x
    +Func Ternär (5) needs for 1048575 runs 3625.00 ms (Average: 0.0035 ms) --> 1.0x

    Zitat

    >Parameter: False
    !Func If x = True Then (1) needs for 1048575 runs 3710.02 ms (Average: 0.0035 ms) --> 1.0x
    -Func If x <> True Then (2) needs for 1048575 runs 3691.45 ms (Average: 0.0035 ms) --> 1.0x
    -Func Switch (3) needs for 1048575 runs 3667.33 ms (Average: 0.0035 ms) --> 1.0x
    -Func Select (4) needs for 1048575 runs 3698.65 ms (Average: 0.0035 ms) --> 1.0x
    +Func Ternär (5) needs for 1048575 runs 3578.56 ms (Average: 0.0034 ms) --> 1.0x

    For/While/Do

    For ist am schnellsten, While/Do geben sich nicht viel

    Zitat

    >Parameter: 100
    +Func For (1) needs for 4095 runs 842.98 ms (Average: 0.2059 ms) --> 1.0x
    -Func While (2) needs for 4095 runs 1049.78 ms (Average: 0.2564 ms) --> 1.2x
    !Func Do (3) needs for 4095 runs 1064.12 ms (Average: 0.2599 ms) --> 1.3x

    ListView


    Sollte selbsterklärend und auch nicht überraschend sein, Begin/EndUpdate schlägt hier alles obwohl die Nativen Funktionen nur kurz dahinter sind

    Zitat

    -Func GUICtrlCreateListViewItem (1) needs for 255 runs 3129.61 ms (Average: 12.2730 ms) --> 1.1x
    !Func _GUICtrlListView_AddItem (2) needs for 255 runs 6763.17 ms (Average: 26.5222 ms) --> 2.3x
    +Func _GUICtrlListView_AddItem BeginUpdate (3) needs for 255 runs 2901.29 ms (Average: 11.3776 ms) --> 1.0x

    Strukturen

    DllSructSetData mit Index ist am schnellsten, danach DllstructSetData mit Namen (Bezeichnung). Die in der euen AutoIt Version eingeführte Möglichkeit Strukturen direkt aufzurufen sieht zwar schick aus im Code ist aber am langsamsten ($tStruct.Name = 123)

    Fortsetzung folgt (hoffentlich auch durch euch :) )

    Gruß,
    Spider

  • Hi,

    alles, was nicht über interne (kompilierte oder API) Funktionen läuft, wird bei AutoIt interpretiert.
    Somit gilt bspw. für sämtliche in Schleifen verarbeitete mathematische Funktionen (sin, cos, tan, +-*/ usw.) und auch für sonstigen "normalen" Code:
    Weniger = Schnellerer

    Generell sollte man sein Script profilen und den "inner loop" suchen. Meist sind es nur eine Handvoll Zeilen, welche die Scriptlaufzeit wirklich beeinflussen.
    Wenn es wirklich auf die Millisekunde ankommt, diskutiert man erst gar keine AutoIt-interne Abwicklung, sondern schreibt den "inner loop" in eine FreeBasic/C(++)/ASM/wieauchimmer kompilierte DLL und ruft diese per DllCall() auf. Thema erledigt.

    • Offizieller Beitrag

    Hallo,

    Dem kann ich nur bedingt zu stimmen. Natürlich stimmt es, dass AutoIt nicht für wirklich rechenintensive Aufgaben geeignet ist, sehr wohl aber für eine schnelle Benutzeroberfläche, wenn man es richtig angeht.
    Ich habe z.B. ein Projekt geschrieben, welches auf einem Low Level PC laufen soll (Atom 1,4 GHz; 1gb ram; win7) und welches Live Daten via WinPcap interpretiert und anzeigt und das in Millisekunden raster. Das habe ich nur geschafft in dem ich an jeder Kleinigkeit herum experimentiert habe um es so effizient wie nur möglich zu gestalten, da hier wirklich sehr viel interpretiert wird.

    Klar beim If/Switch Fall ist es relativ egal, man sieht ja das bei einem Aufruf der unterschied bei 0,002 ms ist, aber z.B. die Geschichte mit der For/While Schleife war hier sehr hilfreich und wenn ich nun 50 Interpretationsfunktionen habe, in denen ich alle von While auf For wechsle sind das gut und gerne paar Millisekunden die man direkt merkt.

    Ein paar Daten die da durchlaufen müssen allerdings noch entschlüsselt und entpackt werden, das lagere ich natürlich hier auch auf C aus, da hatte AutoIt keine Chance.

    Trotzdem: Ich arbeite mit AutoIt nach wie vor sehr gerne wenn es um grafische Angelegenheiten geht, da ist AutoIt C/C++ weit überlegen was die Einfachheit angeht; wenn der Kunde ein Änderungswunsch hat --> kein Problem, eine Stunde später steht die neue GUI.

    Deswegen dieser Thread, ich dachte mir falls jemand ein ähnliches Vorhaben hat kann er/sie sich hier paar Tips abholen ;)

    Gruß'
    Spider

  • Klar beim If/Switch Fall ist es relativ egal, man sieht ja das bei einem Aufruf der unterschied bei 0,002 ms ist, aber z.B. die Geschichte mit der For/While Schleife war hier sehr hilfreich und wenn ich nun 50 Interpretationsfunktionen habe, in denen ich alle von While auf For wechsle sind das gut und gerne paar Millisekunden die man direkt merkt.

    Genau DAS meine ich mit dem finden des "inner loop"!
    Wenn ich Millisekunden schinden will/muss, dann kommt die Schleife natürlich mit in die entweder per DllCall() / DllCallAddress() angesprochene Funktion!
    Es bleibt auszuprobieren, ob der overhead von DllCall() / DllCallAddress() nicht den Gewinn über die schnellere Ausführungszeit auffrisst!

    Das habe ich nur geschafft in dem ich an jeder Kleinigkeit herum experimentiert habe um es so effizient wie nur möglich zu gestalten, da hier wirklich sehr viel interpretiert wird.

    Ein langsam programmiertes Programm bzw. Algorithmus bleibt auch langsam, wenn man es auf einem schnellen Rechner oder mit einem schnellen Compiler einsetzt.
    Wenn man, so wie du es gemacht hast, den Algorihmus bzw. den Programmablauf ständig mit Hinblick auf Geschwindigkeit optimiert, ist man schon auf dem richtigen Weg.
    Ich habe allerdings die Erfahrung gemacht, dass sich kaum jemand diese Mühe macht....intensives profiling ist auch nicht jedermanns Sache!

    Ich behaupte mal, dass AutoIt idR. schnell genug ist, gerade im Hinblick auf

    eine schnelle Benutzeroberfläche, wenn man es richtig angeht.

    Die Frage bleibt, ob man, wenn man wirklich bis auf die letzte Rille optimieren muss, nicht einfach nur die GUI und das drumherum in AutoIt programmiert, und den (idR wenigen) zeitintensiven Rest dann "auslagert".

  • Ich denke die wichtigste Regel sollte sein, dass man immer erst zu erst auf Algorithmenebene optimieren sollte bevor man sich an die Optimierung auf der Codeebene macht.
    Die Frage ob man 20 ms bei 1000 Schleifendurchgängen rausholen kann durch z.B. eine For-To statt While-Schleife wird hinfällig wenn man den Programmablauf auch so realisieren kann, dass man nur 100 Schleifendurchgänge braucht.

    • Offizieller Beitrag

    Hey,

    Klar, habt ihr beide recht mit. Irgendwann ist der Algorhytmus (gerade bei sowas wie ein Sniffer) aber Optimiert bis zum geht nicht mehr und dann kann es schon interessant werden ob z.B. GuiCtrlListViewCreate oder _GuiCtrlListView_Create schneller ist. In der Live Darstellung (>30 Aktualisierungen pro Sekunde) geht da bei nicht optimierter Programmierung schnell AutoIt in die Knie (unabhängig vom Prozessor).

    Auch wenn ich C und ein großteil von C++ beherrsche, lager ich gerne trotzdem viele einfache Sachen noch in AutoIt aus, ganz einfach wegen der Einfachheit der Sprache.

    Ich wollte hier aber auch keine Diskussion auslösen, ob man AutoIt für rechenintensive Vorgänge benutzen sollte oder nicht, manche tuns mehr, manche weniger, die Diskussion hatten wir schon oft genug hier im Forum, also lassen wir es jetzt bitte darauf beruhen.

    Falls jemand noch Ideen zur Codeoptimierung hat, natürlich immer gerne her damit :)

    lg,
    Spider

  • @GTA:
    Bei Ram, muss Du da nicht noch mal 1,024 nehmen, da er jetzt, wenn ich das richtig sehe, ein GB als 1.000 sieht und nicht als 1.024. Richtig oder falsch? Bei mit zeigt er 5,87 GB RAM an, mal 1,024 = 6,01 GB RAM, was dann dem Weit näher kommt.

    Lieben Gruß,
    Alina

    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    Geheime Information: ;)
    OuBVU5ebLhHu5QvlnAyQB4A7SzBrvWulwL7RLl2BdH5tI6sIYspeMKeXMSXl

    • Offizieller Beitrag

    Nein Alina, er teilt das Ergebnis von MemGetStats zweimal durch 1024 und das ist korrekt, da MemGetStats das Ergebnis als KB zurückgibt. Durch 1024 geteilt ergibt MB und nochmal durch 1024 geteilt ergibt GB.

    GtaSpider : Bei mir erscheint als Ergebnis Deines Testscript auch, das For...Next am schnellsten ist, aber in der Schleife wird ja auch nichts ausgeführt.
    Wenn man das Script etwas abwandelt (praxisnäher gestaltet), dann relativiert sich das Ergebnis etwas. For...Next ist zwar noch immer am schnellsten, aber nicht derart krass.

    Spoiler anzeigen
    [autoit]


    ;By GtaSpider
    #include <Array.au3>

    [/autoit] [autoit][/autoit] [autoit]

    #Region Hier ggf Daten verändern
    Global Const $iNumberOfTestFuncs = 3;Wie viele Funktionen es gibt (werden benannt nach _TestFunc_1, _TestFunc_2, ..., _TestFunc_N)
    Global Const $iNumberOfTestRuns = 10;0xfff;Wie viele Tests pro Funktion

    [/autoit] [autoit][/autoit] [autoit]

    Global Const $aFuncNames[$iNumberOfTestFuncs + 1] = ["", _
    "For", _;Funktionsname 1
    "While", _;Funktionsname 2
    "Do"];Funktionsname 3

    [/autoit] [autoit][/autoit] [autoit]

    Global Const $vParam = 1000;Parameter für die Funktionen

    [/autoit] [autoit][/autoit] [autoit]

    Func _TestFunc_1($vParam = False);funktion 1
    Local $j
    For $i = 1 To $vParam
    $j += 1
    ToolTip($j)
    Next
    ToolTip('')
    EndFunc ;==>_TestFunc_1

    [/autoit] [autoit][/autoit] [autoit]

    Func _TestFunc_2($vParam = False);funktion 2
    Local $i
    While $i < $vParam
    $i += 1
    ToolTip($i)
    WEnd
    ToolTip('')
    EndFunc ;==>_TestFunc_2

    [/autoit] [autoit][/autoit] [autoit]

    Func _TestFunc_3($vParam = False);funktion 3
    Local $i
    Do
    $i += 1
    ToolTip($i)
    Until $i >= $vParam
    ToolTip('')
    EndFunc ;==>_TestFunc_3

    [/autoit] [autoit][/autoit] [autoit]

    Func _TestFunc_4($vParam = False);funktion 4
    ;...
    EndFunc ;==>_TestFunc_4

    [/autoit] [autoit][/autoit] [autoit]

    ;...

    [/autoit] [autoit][/autoit] [autoit]

    #EndRegion Hier ggf Daten verändern

    [/autoit] [autoit][/autoit] [autoit]

    _Main()

    [/autoit] [autoit][/autoit] [autoit]

    Func _Main()
    Local $hTimer, $nFuncCounter, $nTestCounter, $iMinIndex, $iMaxIndex, $sCPUname, $aMemStats, $sAutX64
    Local $aiTimers[$iNumberOfTestFuncs + 1] = [$iNumberOfTestFuncs]

    [/autoit] [autoit][/autoit] [autoit]

    ConsoleWrite(StringFormat(">Start %d number of funcs á %d number of tests with parameter %s\r\n", $iNumberOfTestFuncs, $iNumberOfTestRuns, $vParam))

    [/autoit] [autoit][/autoit] [autoit]

    For $nFuncCounter = 1 To $iNumberOfTestFuncs
    ConsoleWrite(StringFormat(">Start Test %d/%d", $nFuncCounter, $iNumberOfTestFuncs))
    $hTimer = TimerInit()
    #Region SAFE
    For $nCounter = 1 To $iNumberOfTestRuns
    Call("_TestFunc_" & $nFuncCounter, $vParam)
    Next
    $aiTimers[$nFuncCounter] = TimerDiff($hTimer)
    #EndRegion SAFE
    ConsoleWrite(StringFormat(" --> %f ms\r\n", $aiTimers[$nFuncCounter]))
    Next

    [/autoit] [autoit][/autoit] [autoit]

    $iMinIndex = _ArrayMinIndex($aiTimers, 1, 1)
    $iMaxIndex = _ArrayMaxIndex($aiTimers, 1, 1)

    [/autoit] [autoit][/autoit] [autoit]

    $sCPUname = RegRead("HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System\CentralProcessor\0", "ProcessorNameString")
    $aMemStats = MemGetStats()
    $sAutX64 = "x86"
    If @AutoItX64 Then $sAutX64 = "x64"

    [/autoit] [autoit][/autoit] [autoit]

    ;Geht nicht da wir abwärtskompatibel bleiben sollten. (Ternärer Operator)
    ;~ ConsoleWrite(StringFormat("\r\n>Result with AutoIt %s %s.\r\n>CPU: %s %s\r\n>RAM: %.2f/%.2f GB\r\n>OS: %s (%d) %s\r\n", @AutoItVersion, (@AutoItX64 ? "x64" : "x86"), $sCPUname, @CPUArch, $aMemStats[2] / 1024 / 1024, $aMemStats[1] / 1024 / 1024, @OSVersion, @OSBuild, @OSArch))

    [/autoit] [autoit][/autoit] [autoit]

    ConsoleWrite(StringFormat("\r\n>Result with AutoIt %s %s.\r\n>CPU: %s %s\r\n>RAM: %.2f/%.2f GB\r\n>OS: %s (%d) %s\r\n>Parameter: %s\r\n", @AutoItVersion, $sAutX64, $sCPUname, @CPUArch, $aMemStats[2] / 1024 / 1024, $aMemStats[1] / 1024 / 1024, @OSVersion, @OSBuild, @OSArch,$vParam))

    [/autoit] [autoit][/autoit] [autoit]

    For $nFuncCounter = 1 To $iNumberOfTestFuncs
    Switch $nFuncCounter
    Case $iMinIndex
    ConsoleWrite("+");schnellster bekommt ein Plus (Grün in SciTE)
    Case $iMaxIndex
    ConsoleWrite("!");langsamster wird Rot dargestellt
    Case Else
    ConsoleWrite("-");Neutral Gelb
    EndSwitch
    ConsoleWrite(StringFormat("Func %s (%d)\tneeds for %d runs %.2f ms (Average: %.4f ms)\t--> ", $aFuncNames[$nFuncCounter], $nFuncCounter, $iNumberOfTestRuns, $aiTimers[$nFuncCounter], $aiTimers[$nFuncCounter] / $iNumberOfTestRuns))
    Switch $nFuncCounter
    Case $iMinIndex
    ConsoleWrite(StringFormat("1.0x\r\n"))
    Case Else
    ConsoleWrite(StringFormat("%.1fx\r\n", $aiTimers[$nFuncCounter] / $aiTimers[$iMinIndex]))
    EndSwitch

    [/autoit] [autoit][/autoit] [autoit]

    Next
    ConsoleWrite(StringFormat("\r\n"))
    EndFunc ;==>_Main

    [/autoit]
  • Na dann denke ich wir fangen am besten mal mit dem Thema Schleifenoptimierung an, da dort wohl eine Menge Potential schlummert.
    Ich gehe einfach mal einige übliche Optimierungen durch. Ich zeige dabei jeweils erst die unoptimierte Variante und dem gegenüber die optimierte. Nicht alles bringt auf AutoIt etwas (besonders die für Cache-optimierenden Verfahren). Deswegen hänge ich immer direkt daran ein kleines Testskript wo man das Potential bei sich selbst schnell ermitteln kann.

    Fangen wir an:

    Loop-unrolling

    Ziel ist es die Anzahl der Schleifendurchläufe zu verringern. In der Theorie ist das ganze schneller, da die Anzahl der Prüfungen der Schleifenbedingungen geringer sind und somit weniger Overhead entsteht. Das ganze geht aber einher mit einer Vervielfachung von Code und funktioniert nur, wenn die obere Schleifengrenze ein Vielfaches des "unroll factor" ist. In AutoIt bringt dieses Verfahren eher nix und kann das Skript sogar verlangsamen.

    Beispiel
    [autoit]

    Global $N = 1e4 + 3
    Global $A[$N + 1]

    [/autoit] [autoit][/autoit] [autoit]

    For $i = 0 To $N
    $A[$i] = "x"
    Next

    [/autoit] [autoit][/autoit] [autoit]

    ; ist equivalent zu:

    [/autoit] [autoit][/autoit] [autoit]

    For $i = 0 To $N Step 4
    $A[$i] = "x"
    $A[$i + 1] = "x"
    $A[$i + 2] = "x"
    $A[$i + 3] = "x"
    Next

    [/autoit]
    Zeitmessung
    [autoit]

    Global $N = 1e6 + 3
    Global $A[$N + 1]

    [/autoit] [autoit][/autoit] [autoit]

    $iT = TimerInit()
    For $i = 0 To $N
    $A[$i] = "x"
    Next
    $iT = TimerDiff($iT)
    ConsoleWrite(StringFormat("% 20s: %8.4f ms\n", "Normale Schleife", $iT))

    [/autoit] [autoit][/autoit] [autoit]

    $iT = TimerInit()
    For $i = 0 To $N Step 4
    $A[$i] = "x"
    $A[$i + 1] = "x"
    $A[$i + 2] = "x"
    $A[$i + 3] = "x"
    Next
    $iT = TimerDiff($iT)
    ConsoleWrite(StringFormat("% 20s: %8.4f ms\n", "Optimierte Schleife", $iT))

    [/autoit]
    Loop Interchange

    Ziel ist es, bei verschachtelten Schleifen, die Anzahl der Schleifeninitialisierungen, und damit Overhead, zu verringern. Unterscheiden sich die Anzahlen der Schleifendurchläufe der inneren und der äußeren Schleife stark, macht es Sinn die Schleife mit der kleineren Anzahl nach außen zu holen um somit die Anzahl der inneren Schleifenaufrufe zu minimieren. Heißt: Die Anzahl der Schleifeninitialisierungen wird verringert, die Anzahl der gesamten Schleifendurchgänge bleibt dennoch konstant.

    Beispiel
    [autoit]

    Global Const $u = 1e2 ; klein
    Global Const $v = 1e6 ; groß

    [/autoit] [autoit][/autoit] [autoit]

    For $i = 1 To $v
    For $j = 1 To $u
    Next
    Next

    [/autoit] [autoit][/autoit] [autoit]

    ; ist equivalent zu (wenn in der Schleife keine Abhängigkeiten vorhanden sind):

    [/autoit] [autoit][/autoit] [autoit]

    For $j = 1 To $u
    For $i = 1 To $v
    Next
    Next

    [/autoit]
    Zeitmessung
    [autoit]

    Global Const $u = 1e2
    Global Const $v = 1e6

    [/autoit] [autoit][/autoit] [autoit]

    $iT = TimerInit()
    For $i = 1 To $v
    For $j = 1 To $u
    Next
    Next
    $iT = TimerDiff($iT)
    ConsoleWrite(StringFormat("% 20s: %8.4f ms\n", "Innen klein", $iT))

    [/autoit] [autoit][/autoit] [autoit]

    $iT = TimerInit()
    For $j = 1 To $u
    For $i = 1 To $v
    Next
    Next
    $iT = TimerDiff($iT)
    ConsoleWrite(StringFormat("% 20s: %8.4f ms\n", "Außen klein", $iT))

    [/autoit]
    Array Cache-Order Looping

    Diese Optimierung kann sich mit dem, eben erwähnten, "Loop-unrolling" widersprechen. Wenn Elemente hintereinander im Speicher stehen und einzeln abgearbeitet werden, holt die CPU diese nicht einzeln aus dem Speicher sondern lädt gleich einen Block von Ihnen in seinen Cache und kann die nächsten Elemente so deutlich schneller abarbeiten. Es macht also Sinn seine Schleifen so aufzubauen, dass nacheinander Elemente abgearbeitet werden welche auch hintereinander im Speicher stehen. Besonders bei 2D-Arrays taucht daher die Frage auf: Gehe ich das Array zeilen- oder spaltenweise durch? C speichert z.B. seine Arrays reihenweise hintereinander (row-major-order) während Fortran diese spaltenweise hintereinander schreibt (column-major-order). In AutoIt ist die Sache durch den Variant-Datentyp zwar nicht ganz ideal für Cache-Optimierung aber prinzipiell sollte man AutoIt für C-Order (row-major) hin optimieren.

    Beispiel
    [autoit]

    Global $N = 1000
    Global $A[$N+1][$N+1]

    [/autoit] [autoit][/autoit] [autoit]

    Global $x

    [/autoit] [autoit][/autoit] [autoit]

    ; optimal für column-major-order:
    For $i = 0 To $N
    For $j = 0 To $N
    $x = $A[$j][$i]
    Next
    Next

    [/autoit] [autoit][/autoit] [autoit]

    ; ist equivalent zu:

    [/autoit] [autoit][/autoit] [autoit]

    ; optimal für row-major-order:
    For $i = 0 To $N
    For $j = 0 To $N
    $x = $A[$i][$j]
    Next
    Next

    [/autoit]
    Zeitmessung
    [autoit]

    Global $N = 3500
    Global $A[$N+1][$N+1]

    [/autoit] [autoit][/autoit] [autoit]

    Global $x

    [/autoit] [autoit][/autoit] [autoit]

    $iT = TimerInit()
    For $i = 0 To $N
    For $j = 0 To $N
    $x = $A[$j][$i]
    Next
    Next
    $iT = TimerDiff($iT)
    ConsoleWrite(StringFormat("% 20s: %8.4f ms\n", "Column-Major-Loop", $iT))

    [/autoit] [autoit][/autoit] [autoit]

    $iT = TimerInit()
    For $i = 0 To $N
    For $j = 0 To $N
    $x = $A[$i][$j]
    Next
    Next
    $iT = TimerDiff($iT)
    ConsoleWrite(StringFormat("% 20s: %8.4f ms\n", "Row-Major-Loop", $iT))

    [/autoit]
    Loop fusion

    Wenn man zwei voneinander unabhängige Schleifen mit dem selben Iterationsbereich hat lohnt es sich diese beiden zu kombinieren um somit den Schleifenoverhead zu halbieren. Das Gegenteil "loop fission" (also eine Schleife in mehrere aufteilen) kann skurrilerweise auch den Code beschleunigen, nämlich wenn dadurch Elemente cache-optimiert abgearbeitet werden können während sie sich in der kombinierten Schleife gegenseitig um den Cache prügeln.

    Beispiel
    [autoit]

    Global $N = 1e6
    Global $A[$N + 1]
    Global $B[$N + 1]

    [/autoit] [autoit][/autoit] [autoit]

    For $i = 0 To $N
    $A[$i] = "x"
    Next
    For $i = 0 To $N
    $B[$i] = "x"
    Next

    [/autoit] [autoit][/autoit] [autoit]

    ; ist equivalent zu:

    [/autoit] [autoit][/autoit] [autoit]

    For $i = 0 To $N
    $A[$i] = "x"
    $B[$i] = "x"
    Next

    [/autoit]
    Zeitmessung
    [autoit]

    Global $N = 1e6
    Global $A[$N + 1]
    Global $B[$N + 1]

    [/autoit] [autoit][/autoit] [autoit]

    $iT = TimerInit()
    For $i = 0 To $N
    $A[$i] = "x"
    Next
    For $i = 0 To $N
    $B[$i] = "x"
    Next
    $iT = TimerDiff($iT)
    ConsoleWrite(StringFormat("% 20s: %8.4f ms\n", "Zwei Schleifen", $iT))

    [/autoit] [autoit][/autoit] [autoit]

    $iT = TimerInit()
    For $i = 0 To $N
    $A[$i] = "x"
    $B[$i] = "x"
    Next
    $iT = TimerDiff($iT)
    ConsoleWrite(StringFormat("% 20s: %8.4f ms\n", "Kombinierte Schleife", $iT))

    [/autoit]
    Loop peeling

    Manche Schleifen, besonders welche, deren Werte iterativ aufeinander aufbauen, erfordern eine gesonderte Behandlung der ersten Werte. Vielfach wird einfach in der Schleife eine Abfrage hierfür eingebaut oder die Berechnung wird durch aufwändigere Konstrukte oder Hilfsvariablen realisiert. Einfacher und performanter ist es jedoch oft, die Behandlung der Spezialfälle aus der Schleife zu nehmen und die eigentliche Schleife damit zu vereinfachen.

    Beispiel
    [autoit]

    Global $N = 1e5
    Global $A[$N+1]

    [/autoit] [autoit][/autoit] [autoit]

    For $i = 0 To $N
    If $i = 0 Then
    $A[$i] = 0
    Else
    $A[$i] = $i + $A[$i-1]
    EndIf
    Next

    [/autoit] [autoit][/autoit] [autoit]

    ; ist equivalent zu:

    [/autoit] [autoit][/autoit] [autoit]

    $A[0] = 0
    For $i = 1 To $N
    $A[$i] = $i + $A[$i-1]
    Next

    [/autoit]
    Zeitmessung
    [autoit]

    Global $N = 1e4
    Global $A[$N+1]

    [/autoit] [autoit][/autoit] [autoit]

    $iT = TimerInit()
    For $i = 0 To $N
    If $i = 0 Then
    $A[$i] = 0
    Else
    $A[$i] = $i + $A[$i-1]
    EndIf
    Next
    $iT = TimerDiff($iT)
    ConsoleWrite(StringFormat("% 30s: %8.4f ms\n", "Sonderbehandling in Schleife", $iT))

    [/autoit] [autoit][/autoit] [autoit]

    $iT = TimerInit()
    $A[0] = 0
    For $i = 1 To $N
    $A[$i] = $i + $A[$i-1]
    Next
    $iT = TimerDiff($iT)

    [/autoit] [autoit][/autoit] [autoit]

    ConsoleWrite(StringFormat("% 30s: %8.4f ms\n", "Sonderbehandlung außerhalb der Schleife", $iT))

    [/autoit]
    loop invariant variables

    Die meiner Meinung nach wichtigste Regel bei der Codeoptimierung lautet: Was du einmal berechnet/ermittelt hast, hebe dir für die spätere Verwendung auf anstatt es neu zu berechnen. Angewandt auf Schleifen heißt das: Ausdrücke, deren Wert innerhalb einer Schleife konstant bleiben ("loop invariant") sollten aus der Schleife herausgeholt werden.

    Beispiel
    [autoit]

    Global $N = 1e6
    Global $A[$N+1]

    [/autoit] [autoit][/autoit] [autoit]

    For $i = 0 To $N
    $A[$i] = $i * 2 * ACos(-1)
    Next

    [/autoit] [autoit][/autoit] [autoit]

    ; ist equivalent zu:

    [/autoit] [autoit][/autoit] [autoit]

    Global Const $2Pi = 2 * ACos(-1)
    For $i = 0 To $N
    $A[$i] = $i * $2Pi
    Next

    [/autoit]
    Zeitmessung
    [autoit]

    Global $N = 1e6
    Global $A[$N+1]

    [/autoit] [autoit][/autoit] [autoit]

    $iT = TimerInit()
    For $i = 0 To $N
    $A[$i] = $i * 2 * ACos(-1)
    Next
    $iT = TimerDiff($iT)
    ConsoleWrite(StringFormat("% 30s: %8.4f ms\n", "Neuberechnung von 2Pi", $iT))

    [/autoit] [autoit][/autoit] [autoit]

    $iT = TimerInit()
    Global Const $2Pi = 2 * ACos(-1)
    For $i = 0 To $N
    $A[$i] = $i * $2Pi
    Next
    $iT = TimerDiff($iT)
    ConsoleWrite(StringFormat("% 30s: %8.4f ms\n", "Wiederverwendung von 2Pi", $iT))

    [/autoit]
    strength reduction

    Schleifen kann man dazu nutzen Ausdrücke in iterative Ausdrücke so umzuschreiben, dass ihre Berechnung pro Durchgang weniger aufwändig wird. So bietet es sich z.B. oft an, Multiplikationen der Iterationsvariable durch eine Addition (mit Hilfe einer Hilfsvariablen) zu ersetzen:

    Beispiel
    [autoit]

    Global $N = 1e6
    Global $A[$N + 1]
    Global $x

    [/autoit] [autoit][/autoit] [autoit]

    $x = 4 ; der Multiplikationsfaktor
    For $i = 0 To $N
    $A[$i] = $x * $i
    Next

    [/autoit] [autoit][/autoit] [autoit]

    ; ist equivalent zu:

    [/autoit] [autoit][/autoit] [autoit]

    $x = 0
    For $i = 0 To $N
    $A[$i] = $x
    $x += 4
    Next

    [/autoit]
    Zeitmessung
    [autoit]

    Global $N = 1e6
    Global $A[$N + 1]
    Global $x

    [/autoit] [autoit][/autoit] [autoit]

    $iT = TimerInit()
    $x = 4 ; der Multiplikationsfaktor
    For $i = 0 To $N
    $A[$i] = $x * $i
    Next
    $iT = TimerDiff($iT)
    ConsoleWrite(StringFormat("% 30s: %8.4f ms\n", "Multiplikation", $iT))

    [/autoit] [autoit][/autoit] [autoit]

    $iT = TimerInit()
    $x = 0
    For $i = 0 To $N
    $A[$i] = $x
    $x += 4
    Next
    $iT = TimerDiff($iT)
    ConsoleWrite(StringFormat("% 30s: %8.4f ms\n", "Addition", $iT))

    [/autoit]

    2 Mal editiert, zuletzt von AspirinJunkie (11. Dezember 2014 um 12:55) aus folgendem Grund: Forum hat die StringFormat-Anweisung falsch dargestellt.

    • Offizieller Beitrag

    AspirinJunkie: Interessante Optimierungsmöglichkeiten! Einiges davon habe ich auch noch nicht gewusst. :thumbup:
    Was mir aber bei Deinen Beispielen noch aufgefallen ist, ist, dass die Beispiele im X64-Modus (AutoIt v3.3.12.0) schneller abgearbeitet werden.
    Also mit:

    [autoit]

    #AutoIt3Wrapper_UseX64=y

    [/autoit]


    in der ersten Zeile.

  • Hier mal ein paar weitere Methoden. Diesmal thematisch jedoch eher durcheinander:

    inline functions

    Jeder Aufruf einer Funktion bedingt einen Overhead welcher das Skript verlangsamt. Gerade bei kleineren Funktionen welche nicht oft im Code aufgerufen werden bietet es sich an deren Inhalt direkt in den Code zu schreiben um den Funktionsoverhead zu vermeiden.

    Beispiel (erstes Array-Element bis zum Ende durchreichen)
    [autoit]

    Global $N = 1e6
    Global $A[$N + 1]

    [/autoit] [autoit][/autoit] [autoit]

    For $i = 1 To $N
    __swap($A, $i, $i - 1)
    Next

    [/autoit] [autoit][/autoit] [autoit]

    ; tauscht zwei Elemente in einem Array
    Func __swap(ByRef $A, Const $i, Const $j)
    Local Const $t = $A[$i]
    $A[$i] = $A[$j]
    $A[$j] = $t
    EndFunc ;==>__swap

    [/autoit] [autoit][/autoit] [autoit]

    ; ist equivalent zu:

    [/autoit] [autoit][/autoit] [autoit]

    Global $t
    For $i = 1 To $N
    $t = $A[$i]
    $A[$i] = $A[$i - 1]
    $A[$i - 1] = $t
    Next

    [/autoit]
    Zeitmessung
    [autoit]

    Global $N = 1e6
    Global $A[$N + 1]

    [/autoit] [autoit][/autoit] [autoit]

    $iT = TimerInit()
    For $i = 1 To $N
    __swap($A, $i, $i - 1)
    Next
    $iT = TimerDiff($iT)
    ConsoleWrite(StringFormat("%30s: %8.4f ms\n", "Mit Funktionsaufruf", $iT))

    [/autoit] [autoit][/autoit] [autoit]

    $iT = TimerInit()
    Global $t
    For $i = 1 To $N
    $t = $A[$i]
    $A[$i] = $A[$i - 1]
    $A[$i - 1] = $t
    Next
    $iT = TimerDiff($iT)
    ConsoleWrite(StringFormat("%30s: %8.4f ms\n", "Inline-function", $iT))

    [/autoit] [autoit][/autoit] [autoit]

    ; tauscht zwei Elemente in einem Array
    Func __swap(ByRef $A, Const $i, Const $j)
    Local Const $t = $A[$i]
    $A[$i] = $A[$j]
    $A[$j] = $t
    EndFunc ;==>__swap

    [/autoit]
    Kurzschlussauswertung (short-circuit-evaluation

    Bei bedingten Ausdrücken mit logischem "and" oder "or" wird zur Prüfung der Bedingung "x and y" immer erst beide Seiten ausgewertet bevor die Entscheidung zur Annahme fällt oder nicht. Wenn aber x schon nicht true ergibt, braucht man eigentlich y nicht mehr auszuwerten denn der Gesamtausdruck kann gar nicht mehr wahr werden. Die Idee hinter der short-circuit-evaluation ist es daher die logischen Ausdrücke in verschachtelte If-Else-Verzweigungen so umzuwandeln, dass die Auswertung der Einzelausdrücke nur dann gemacht wird wenn es auch sinnvoll ist.

    Beispiel (Gemeinsames Vielfaches von 3 und 5)
    [autoit]

    #Region Short-Circuit-Optimierung für And-Verknüpfung

    [/autoit] [autoit][/autoit] [autoit]

    If (Mod($i, 5) = 0) And (Mod($i, 3) = 0) Then
    $x = $i
    EndIf

    [/autoit] [autoit][/autoit] [autoit]

    ; ist equivalent zu:

    [/autoit] [autoit][/autoit] [autoit]

    If Mod($i, 5) = 0 Then
    If Mod($i, 3) = 0 Then
    $x = $i
    EndIf
    EndIf

    [/autoit] [autoit][/autoit] [autoit]

    #EndRegion Short-Circuit-Optimierung für And-Verknüpfung

    [/autoit] [autoit][/autoit] [autoit]

    #Region Short-Circuit-Optimierung für Or-Verknüpfung

    [/autoit] [autoit][/autoit] [autoit]

    If (Mod($i, 5) = 0) Or (Mod($i, 3) = 0) Then
    $x = $i
    EndIf

    [/autoit] [autoit][/autoit] [autoit]

    ; ist equivalent zu:

    [/autoit] [autoit][/autoit] [autoit]

    If Mod($i, 5) = 0 Then
    $x = $i
    ElseIf Mod($i, 3) = 0 Then
    $x = $i
    EndIf

    [/autoit] [autoit][/autoit] [autoit]

    #EndRegion Short-Circuit-Optimierung für Or-Verknüpfung

    [/autoit]
    Zeitmessung
    [autoit]

    Global $N = 1e6
    Global $x

    [/autoit] [autoit][/autoit] [autoit]

    #Region Short-Circuit-Optimierung für And-Verknüpfung

    [/autoit] [autoit][/autoit] [autoit]

    $iT = TimerInit()
    For $i = 0 To $N
    If (Mod($i, 5) = 0) And (Mod($i, 3) = 0) Then
    $x = $i
    EndIf
    Next
    $iT = TimerDiff($iT)
    ConsoleWrite(StringFormat("%30s: %8.4f ms\n", "Logisches Und", $iT))

    [/autoit] [autoit][/autoit] [autoit]

    $iT = TimerInit()
    For $i = 0 To $N
    If Mod($i, 5) = 0 Then
    If Mod($i, 3) = 0 Then
    $x = $i
    EndIf
    EndIf
    Next
    $iT = TimerDiff($iT)
    ConsoleWrite(StringFormat("%30s: %8.4f ms\n", "If-Else", $iT))

    [/autoit] [autoit][/autoit] [autoit]

    #EndRegion Short-Circuit-Optimierung für And-Verknüpfung

    [/autoit] [autoit][/autoit] [autoit]

    #Region Short-Circuit-Optimierung für Or-Verknüpfung

    [/autoit] [autoit][/autoit] [autoit]

    $iT = TimerInit()
    For $i = 0 To $N
    If (Mod($i, 5) = 0) Or (Mod($i, 3) = 0) Then
    $x = $i
    EndIf
    Next
    $iT = TimerDiff($iT)
    ConsoleWrite(StringFormat("%30s: %8.4f ms\n", "Logisches Or", $iT))

    [/autoit] [autoit][/autoit] [autoit]

    $iT = TimerInit()
    For $i = 0 To $N
    If Mod($i, 5) = 0 Then
    $x = $i
    ElseIf Mod($i, 3) = 0 Then
    $x = $i
    EndIf
    Next
    $iT = TimerDiff($iT)
    ConsoleWrite(StringFormat("%30s: %8.4f ms\n", "If-ElseIf", $iT))

    [/autoit] [autoit][/autoit] [autoit]

    #EndRegion Short-Circuit-Optimierung für Or-Verknüpfung

    [/autoit]
    For-To vs. For-In

    Wenn man über alle Elemente eines Arrays lesend iterieren möchte, bieten sich sowohl eine For-To-Schleife als auch eine For-In-Schleife an. Bei der For-In wird das jeweils aktuelle Element in die Iterationsvariable kopiert (deswegen nur lesender Zugriff) während man bei For-To mit dem Array-Index arbeit. Machen wir es kurz: For-In ist in AutoIt schneller. Das ist aber nur bei Sprachen wie Autoit der Fall weil dabei der Elementzugriff in nativen Code erfolgt anstatt in noch zu interpretierendem AutoIt-Code. In Sprachen wie C++ oder ähnlichen ist es hingegen langsamer, da noch eine Variablenzuweisung pro Schleifendurchlauf hinzukommt.

    Beispiel
    [autoit]

    Global $N = 1e6
    Global $A[$N + 1]
    Global $x

    [/autoit] [autoit][/autoit] [autoit]

    For $i = 0 To $N
    $x = $A[$i]
    Next

    [/autoit] [autoit][/autoit] [autoit]

    ; ist equivalent zu:

    [/autoit] [autoit][/autoit] [autoit]

    For $i In $A
    $x = $i
    Next

    [/autoit]
    Zeitmessung
    [autoit]

    Global $N = 1e6
    Global $A[$N + 1]
    Global $x

    [/autoit] [autoit][/autoit] [autoit]

    $iT = TimerInit()
    For $i = 0 To $N
    $x = $A[$i]
    Next
    $iT = TimerDiff($iT)
    ConsoleWrite(StringFormat("% 20s: %8.4f ms\n", "For-To", $iT))

    [/autoit] [autoit][/autoit] [autoit]

    $iT = TimerInit()
    For $i In $A
    $x = $i
    Next
    $iT = TimerDiff($iT)
    ConsoleWrite(StringFormat("% 20s: %8.4f ms\n", "For-In", $iT))

    [/autoit]
    Operatoren wie &quot;^&quot; durch weniger aufwendige Berechnungen ersetzen

    Manche Operatoren können in einigen Fällen durch mathematisch äquivalente aber schnellere Ausdrücke ersetzt werden. Bekanntestes Beispiel wäre z.B. x^2 durch x*x zu ersetzen. Da der ^-Operator intern über eine Schleife berechnet (ich weiß es nicht genau) ist die Multiplikation in dem Fall schneller. In anderen Sprachen werden oftmals vor allem Integerberechnungen durch weitaus schnellere Bit-Operationen ersetzt (hier eine Auswahl: >>Link<<) . In AutoIt sind die Bit-Operationen aber als Funktionen implementiert, weswegen deren Overhead durch den Funktionsaufruf den Performancevorteil in der Regel wieder auffrisst.

    Beispiel
    [autoit]

    $x = $i^2

    [/autoit] [autoit][/autoit] [autoit]

    ; ist equivalent zu

    [/autoit] [autoit][/autoit] [autoit]

    $x = $i*$i

    [/autoit]
    Zeitmessung
    [autoit]

    Global $N = 1e6
    Global $x

    [/autoit] [autoit][/autoit] [autoit]

    $iT = TimerInit()
    For $i = 0 To $N
    $x = $i^2
    Next
    $iT = TimerDiff($iT)
    ConsoleWrite(StringFormat("% 20s: %8.4f ms\n", "^-Operator", $iT))

    [/autoit] [autoit][/autoit] [autoit]

    $iT = TimerInit()
    For $i = 0 To $N
    $x = $i*$i
    Next
    $iT = TimerDiff($iT)
    ConsoleWrite(StringFormat("% 20s: %8.4f ms\n", "Multiplikation", $iT))

    [/autoit]
    Hartkodierte Konstanten

    Wenn man Konstanten definiert kann man anstatt auf Ihren Wert per Variablennamen zuzugreifen auch direkt ihren Wert eintragen.

    Beispiel
    [autoit]

    Global Const $PI = ACos(-1)

    [/autoit] [autoit][/autoit] [autoit]

    $x = $i * $PI

    [/autoit] [autoit][/autoit] [autoit]

    ; ist equivalent zu:

    [/autoit] [autoit][/autoit] [autoit]

    $x = $i * 3.14159265358979

    [/autoit]
    Zeitmessung
    [autoit]

    Global $N = 1e6
    Global Const $PI = ACos(-1)
    Global $x

    [/autoit] [autoit][/autoit] [autoit]

    $iT = TimerInit()
    For $i = 0 To $N
    $x = $i * $PI
    Next
    $iT = TimerDiff($iT)
    ConsoleWrite(StringFormat("% 20s: %8.4f ms\n", "Konstante Variable", $iT))

    [/autoit] [autoit][/autoit] [autoit]

    $iT = TimerInit()
    For $i = 0 To $N
    $x = $i * 3.14159265358979
    Next
    $iT = TimerDiff($iT)
    ConsoleWrite(StringFormat("% 20s: %8.4f ms\n", "Direktes Einsetzen ", $iT))

    [/autoit]
    Auf den Datentyp achten

    Integerberechnungen sind (in der Regel) schneller als Float-Berechnungen. Wenn man keine Nachkommastellen benötigt, sollte man auch nicht mit Floats rechnen.

    Beispiel
    [autoit]

    ; Integer-Schleife
    For $i = 0 To $N
    Next

    [/autoit] [autoit][/autoit] [autoit]

    ; vs.

    [/autoit] [autoit][/autoit] [autoit]

    ; Float-Schleife:
    For $i = 0.0 To $N
    Next

    [/autoit]
    Zeitmessung
    [autoit]

    Global $N = 1e7

    [/autoit] [autoit][/autoit] [autoit]

    ; Integer-Schleife
    $iT = TimerInit()
    For $i = 0 To $N
    Next
    $iT = TimerDiff($iT)
    ConsoleWrite(StringFormat("% 20s: %8.4f ms\n", "Integer", $iT))

    [/autoit] [autoit][/autoit] [autoit]

    ; Float-Schleife:
    $iT = TimerInit()
    For $i = 0.0 To $N
    Next
    $iT = TimerDiff($iT)
    ConsoleWrite(StringFormat("% 20s: %8.4f ms\n", "Float", $iT))

    [/autoit]
    Temporäre Variablen vermeiden

    Anstatt Ausdrücke erst in eine Variable auszuwerten und dann weiterzuverwenden, kann man sie auch direkt in den Zielausdruck einfügen.

    Beispiel
    [autoit]

    Global $N = 1e6
    Global $A[$N + 1]
    Global $x, $t

    [/autoit] [autoit][/autoit] [autoit]

    For $i = 0 To $N
    $t = $A[$i]
    $x = $t
    Next

    [/autoit] [autoit][/autoit] [autoit]

    ; ist equivalent zu:

    [/autoit] [autoit][/autoit] [autoit]

    For $i = 0 To $N
    $x = $A[$i]
    Next

    [/autoit]
    Zeitmessung
    [autoit]

    Global $N = 1e6
    Global $A[$N + 1]
    Global $x, $t

    [/autoit] [autoit][/autoit] [autoit]

    $iT = TimerInit()
    For $i = 0 To $N
    $t = $A[$i]
    $x = $t
    Next
    $iT = TimerDiff($iT)
    ConsoleWrite(StringFormat("% 20s: %8.4f ms\n", "Temporäre Variable", $iT))

    [/autoit] [autoit][/autoit] [autoit]

    $iT = TimerInit()
    For $i = 0 To $N
    $x = $A[$i]
    Next
    $iT = TimerDiff($iT)
    ConsoleWrite(StringFormat("% 20s: %8.4f ms\n", "Direktes Einsetzen ", $iT))

    [/autoit]
    2D-Arrays vs. multiple 1D-Arrays

    Anstatt eines großen 2D-Arrays kann es der Performance zuträglich sein, wenn man die Spalten des 2D-Arrays als separate 1D-Arrays vorhält.

    Beispiel
    [autoit]

    Global $N = 1e6
    Global $A[$N + 1][3]
    Global $X[$N + 1]
    Global $Y[$N + 1]
    Global $Z[$N + 1]

    [/autoit] [autoit][/autoit] [autoit]

    For $i = 0 To $N
    $A[$i][0] = $A[$i][1] + $A[$i][2]
    Next

    [/autoit] [autoit][/autoit] [autoit]

    ; vs.

    [/autoit] [autoit][/autoit] [autoit]

    For $i = 0 To $N
    $X[$i] = $Y[$i] + $Z[$i]
    Next

    [/autoit]
    Zeitmessung
    [autoit]

    #Region 2D-Array vs. multiple 1D Arrays
    Global $N = 1e6
    Global $A[$N + 1][3]
    Global $X[$N + 1]
    Global $Y[$N + 1]
    Global $Z[$N + 1]

    [/autoit] [autoit][/autoit] [autoit]

    $iT = TimerInit()
    For $i = 0 To $N
    $A[$i][0] = $A[$i][1] + $A[$i][2]
    Next
    $iT = TimerDiff($iT)
    ConsoleWrite(StringFormat("% 20s: %8.4f ms\n", "2D Array", $iT))

    [/autoit] [autoit][/autoit] [autoit]

    $iT = TimerInit()
    For $i = 0 To $N
    $X[$i] = $Y[$i] + $Z[$i]
    Next
    $iT = TimerDiff($iT)
    ConsoleWrite(StringFormat("% 20s: %8.4f ms\n", "1D Arrays", $iT))
    #EndRegion 2D-Array vs. multiple 1D Arrays

    [/autoit]
    Funktionsparameter By Reference vs. By Value

    Funktionsparameter werden in AutoIt standardmäßig als "By-Value" übergeben. Das heißt, für die Funktion wird eine Kopie der Variable erstellt. Dies ist oftmals unnötig oder sogar kontraproduktiv wenn man z.B. die Werte direkt in der Funktion ändern möchte. Daher gibt es in AutoIt das Schlüsselwort "ByRef" mit welchem man die Parameter als By-Reference markieren kann. Das erspart dem Skript das Kopieren des Wertes und man kann die Ausgangsvariablen direkt bearbeiten.

    Beispiel
    [autoit]

    Global $A[10]
    Global $x

    [/autoit] [autoit][/autoit] [autoit]

    $x = _A($A, 5)

    [/autoit] [autoit][/autoit] [autoit]

    ; Aufruf mit Array per By-Value
    Func _A($A, $i)
    Return $A[$i]
    EndFunc

    [/autoit] [autoit][/autoit] [autoit]

    ; vs.

    [/autoit] [autoit][/autoit] [autoit]

    $x = _B($A, 5)

    [/autoit] [autoit][/autoit] [autoit]

    ; Aufruf mit Array per By-Reference
    Func _B(ByRef $A, $i)
    Return $A[$i]
    EndFunc

    [/autoit]
    Zeitmessung
    [autoit]

    Global $N = 1e6
    Global $A[10]
    Global $x

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    $iT = TimerInit()
    For $i = 1 To $N
    $x = _A($A, 5)
    Next
    $iT = TimerDiff($iT)
    ConsoleWrite(StringFormat("%30s: %8.4f ms\n", "By-Value", $iT))

    [/autoit] [autoit][/autoit] [autoit]

    $iT = TimerInit()
    For $i = 1 To $N
    $x = _B($A, 5)
    Next
    $iT = TimerDiff($iT)
    ConsoleWrite(StringFormat("%30s: %8.4f ms\n", "By-Reference", $iT))

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    ; Aufruf mit Array per By-Value
    Func _A($A, $i)
    Return $A[$i]
    EndFunc

    [/autoit] [autoit][/autoit] [autoit]

    ; Aufruf mit Array per By-Reference
    Func _B(ByRef $A, $i)
    Return $A[$i]
    EndFunc

    [/autoit]

    2 Mal editiert, zuletzt von AspirinJunkie (11. Dezember 2014 um 14:20) aus folgendem Grund: hatte noch zwei vergessen

  • Interessanter Thread!
    Ich selber habe mich noch nie mit der Geschwindigkeit von Programmen befasst. Vielleicht mal wenn es in den Grafischen Bereich ging. Ansonsten war und ist die Geschwindigkeit von AutoIt durchaus ausreichend gewesen. Aber echt interessant was für Kleinigkeiten einige Sekunden einsparen. Ich denke wenn man konsequent dann auf diese kleinen Dinge achtet, dann sollte man einen deutlich merkbaren Unterschied in komplexen Algorithmen spüren. Natürlich sollte man zuerst den Algorithmus an sich verbessern. Wie kommt man eigentlich darauf? Bzw. wo gibt es Lektüre zu diesem Thema? Gibt's da spezielle Begriffe zu um sich das eine oder andere selber zu googeln?

    Um selber mal was beizutragen:
    Wer gerne mit GDI+ arbeitet sollte sich mal überlegen Bitmaps (normalerweise verwendet man ja ein Backbuffer) mit _WinAPI_BitBlt() ins Fenster zu zeichnen. Dies ist um Längen schneller als _GDIPlus_GraphicsDrawImage(). Siehe Codebeispiel:

    Spoiler anzeigen
    [autoit]

    ; ++++++++++ +++++++++ ++++++++ +++++++ ++++++ +++++ ++++ +++ ++ +

    [/autoit] [autoit][/autoit] [autoit]

    #include <GDIPlus.au3>
    #include <WinAPI.au3>
    #include <WindowsConstants.au3>

    [/autoit] [autoit][/autoit] [autoit]

    Global $hGUI, $hGraphic, $hBitmap, $hHBitmap, $hImage, $hDCDest, $hDCSrc, $oObj, $hGfx, $iFor, $hPen, $hTimer

    [/autoit] [autoit][/autoit] [autoit]

    _GDIPlus_Startup()

    [/autoit] [autoit][/autoit] [autoit]

    ; ++++++++++ +++++++++ ++++++++ +++++++ ++++++ +++++ ++++ +++ ++ +

    [/autoit] [autoit][/autoit] [autoit]

    $hGUI = GUICreate('GDI+ Example', 800, 600)
    $hGraphic = _GDIPlus_GraphicsCreateFromHWND($hGUI)
    $hBitmap = _GDIPlus_BitmapCreateFromGraphics(800, 600, $hGraphic)
    $hHBitmap = _GDIPlus_BitmapCreateHBITMAPFromBitmap($hBitmap)
    $hImage = _GDIPlus_ImageGetGraphicsContext($hBitmap)
    $hDCDest = _WinAPI_GetDC($hGUI)
    $hDCSrc = _WinAPI_CreateCompatibleDC($hDCDest)
    $oObj = _WinAPI_SelectObject($hDCSrc, $hHBitmap)
    $hGfx = _GDIPlus_GraphicsCreateFromHDC($hDCSrc)
    GUISetState()

    [/autoit] [autoit][/autoit] [autoit]

    ; ++++++++++ +++++++++ ++++++++ +++++++ ++++++ +++++ ++++ +++ ++ +

    [/autoit] [autoit][/autoit] [autoit]

    _GDIPlus_GraphicsClear($hImage, 0xFFFFFFFF)
    For $iFor = 1 To 100
    $hPen = _GDIPlus_PenCreate(BitOR(0xFF000000, Random(0x000000, 0xFFFFFF, 1)))
    _GDIPlus_GraphicsDrawLine($hImage, Random(0, 800, 1), Random(0, 600, 1), Random(0, 800, 1), Random(0, 600, 1), $hPen)
    _GDIPlus_GraphicsDrawLine($hGfx, Random(0, 800, 1), Random(0, 600, 1), Random(0, 800, 1), Random(0, 600, 1), $hPen)
    _GDIPlus_PenDispose($hPen)
    Next

    [/autoit] [autoit][/autoit] [autoit]

    $hTimer = TimerInit()
    For $iFor = 1 To 1000
    _GDIPlus_GraphicsDrawImage($hGraphic, $hBitmap, 0, 0)
    Next
    ConsoleWrite('_GDIPlus_GraphicsDrawImage => ' & TimerDiff($hTimer) & @CRLF)

    [/autoit] [autoit][/autoit] [autoit]

    $hTimer = TimerInit()
    For $iFor = 1 To 1000
    _WinAPI_BitBlt($hDCDest, 0, 0, 800, 600, $hDCSrc, 0, 0, $SRCCOPY)
    Next
    ConsoleWrite(' _WinAPI_BitBlt => ' & TimerDiff($hTimer) & @CRLF)

    [/autoit] [autoit][/autoit] [autoit]

    ; ++++++++++ +++++++++ ++++++++ +++++++ ++++++ +++++ ++++ +++ ++ +

    [/autoit] [autoit][/autoit] [autoit]

    _GDIPlus_GraphicsDispose($hGfx)
    _WinAPI_SelectObject($hDCSrc, $oObj)
    _WinAPI_DeleteDC($hDCSrc)
    _WinAPI_ReleaseDC($hGUI, $hDCDest)
    _GDIPlus_GraphicsDispose($hImage)
    _WinAPI_DeleteObject($hHBitmap)
    _GDIPlus_BitmapDispose($hBitmap)
    _GDIPlus_GraphicsDispose($hGraphic)
    _GDIPlus_Shutdown()

    [/autoit] [autoit][/autoit] [autoit]

    ; ++++++++++ +++++++++ ++++++++ +++++++ ++++++ +++++ ++++ +++ ++ +

    [/autoit]
    • Offizieller Beitrag

    Hallo,

    Schön! Vielen Dank für das Feedback, da kann man sich doch gut was raus holen :)

    GtaSpider : Bei mir erscheint als Ergebnis Deines Testscript auch, das For...Next am schnellsten ist, aber in der Schleife wird ja auch nichts ausgeführt.
    Wenn man das Script etwas abwandelt (praxisnäher gestaltet), dann relativiert sich das Ergebnis etwas. For...Next ist zwar noch immer am schnellsten, aber nicht derart krass.

    Stimmt; das hab ich nicht beachtet. ToolTip ist Grafisch, hier auch mal mit einer nicht grafischen Funktion

    Script
    [autoit]

    ;By GtaSpider
    #include <Array.au3>

    [/autoit] [autoit][/autoit] [autoit]

    #Region Hier ggf Daten verändern
    Global Const $iNumberOfTestFuncs = 3;Wie viele Funktionen es gibt (werden benannt nach _TestFunc_1, _TestFunc_2, ..., _TestFunc_N)
    Global Const $iNumberOfTestRuns = 0xfff;Wie viele Tests pro Funktion

    [/autoit] [autoit][/autoit] [autoit]

    Global Const $aFuncNames[$iNumberOfTestFuncs + 1] = ["", _
    "For", _;Funktionsname 1
    "While", _;Funktionsname 2
    "Do"];Funktionsname 3

    [/autoit] [autoit][/autoit] [autoit]

    Global Const $vParam = 100

    [/autoit] [autoit][/autoit] [autoit]

    Func _TestFunc_1($vParam = False);funktion 1
    For $i = 1 To $vParam
    Assign("a",$i)
    Next
    EndFunc ;==>_TestFunc_1

    [/autoit] [autoit][/autoit] [autoit]

    Func _TestFunc_2($vParam = False);funktion 2
    Local $i = 0
    While $i < $vParam
    Assign("a",$i)
    $i += 1
    WEnd
    EndFunc ;==>_TestFunc_2

    [/autoit] [autoit][/autoit] [autoit]

    Func _TestFunc_3($vParam = False);funktion 3
    Local $i
    Do
    Assign("a",$i)
    $i+=1
    Until $i = $vParam
    EndFunc ;==>_TestFunc_3

    [/autoit] [autoit][/autoit] [autoit]

    Func _TestFunc_4($vParam = False);funktion 4
    ;...
    EndFunc ;==>_TestFunc_4

    [/autoit] [autoit][/autoit] [autoit]

    ;...

    [/autoit] [autoit][/autoit] [autoit]

    #EndRegion Hier ggf Daten verändern

    [/autoit] [autoit][/autoit] [autoit]

    _Main()

    [/autoit] [autoit][/autoit] [autoit]

    Func _Main()
    Local $hTimer, $nFuncCounter, $nTestCounter, $iMinIndex, $iMaxIndex, $sCPUname, $aMemStats, $sAutX64
    Local $aiTimers[$iNumberOfTestFuncs + 1] = [$iNumberOfTestFuncs]

    [/autoit] [autoit][/autoit] [autoit]

    ConsoleWrite(StringFormat(">Start %d number of funcs á %d number of tests with parameter %s\r\n", $iNumberOfTestFuncs, $iNumberOfTestRuns, $vParam))

    [/autoit] [autoit][/autoit] [autoit]

    For $nFuncCounter = 1 To $iNumberOfTestFuncs
    ConsoleWrite(StringFormat(">Start Test %d/%d", $nFuncCounter, $iNumberOfTestFuncs))
    $hTimer = TimerInit()
    #Region SAFE
    For $nCounter = 1 To $iNumberOfTestRuns
    Call("_TestFunc_" & $nFuncCounter, $vParam)
    Next
    $aiTimers[$nFuncCounter] = TimerDiff($hTimer)
    #EndRegion SAFE
    ConsoleWrite(StringFormat(" --> %f ms\r\n", $aiTimers[$nFuncCounter]))
    Next

    [/autoit] [autoit][/autoit] [autoit]

    $iMinIndex = _ArrayMinIndex($aiTimers, 1, 1)
    $iMaxIndex = _ArrayMaxIndex($aiTimers, 1, 1)

    [/autoit] [autoit][/autoit] [autoit]

    $sCPUname = RegRead("HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System\CentralProcessor\0", "ProcessorNameString")
    $aMemStats = MemGetStats()
    $sAutX64 = "x86"
    If @AutoItX64 Then $sAutX64 = "x64"

    [/autoit] [autoit][/autoit] [autoit]

    ;Geht nicht da wir abwärtskompatibel bleiben sollten. (Ternärer Operator)
    ;~ ConsoleWrite(StringFormat("\r\n>Result with AutoIt %s %s.\r\n>CPU: %s %s\r\n>RAM: %.2f/%.2f GB\r\n>OS: %s (%d) %s\r\n", @AutoItVersion, (@AutoItX64 ? "x64" : "x86"), $sCPUname, @CPUArch, $aMemStats[2] / 1024 / 1024, $aMemStats[1] / 1024 / 1024, @OSVersion, @OSBuild, @OSArch))

    [/autoit] [autoit][/autoit] [autoit]

    ConsoleWrite(StringFormat("\r\n>Result with AutoIt %s %s.\r\n>CPU: %s %s\r\n>RAM: %.2f/%.2f GB\r\n>OS: %s (%d) %s\r\n>Parameter: %s\r\n", @AutoItVersion, $sAutX64, $sCPUname, @CPUArch, $aMemStats[2] / 1024 / 1024, $aMemStats[1] / 1024 / 1024, @OSVersion, @OSBuild, @OSArch,$vParam))

    [/autoit] [autoit][/autoit] [autoit]

    For $nFuncCounter = 1 To $iNumberOfTestFuncs
    Switch $nFuncCounter
    Case $iMinIndex
    ConsoleWrite("+");schnellster bekommt ein Plus (Grün in SciTE)
    Case $iMaxIndex
    ConsoleWrite("!");langsamster wird Rot dargestellt
    Case Else
    ConsoleWrite("-");Neutral Gelb
    EndSwitch
    ConsoleWrite(StringFormat("Func %s (%d)\tneeds for %d runs %.2f ms (Average: %.4f ms)\t--> ", $aFuncNames[$nFuncCounter], $nFuncCounter, $iNumberOfTestRuns, $aiTimers[$nFuncCounter], $aiTimers[$nFuncCounter] / $iNumberOfTestRuns))
    Switch $nFuncCounter
    Case $iMinIndex
    ConsoleWrite(StringFormat("1.0x\r\n"))
    Case Else
    ConsoleWrite(StringFormat("%.1fx\r\n", $aiTimers[$nFuncCounter] / $aiTimers[$iMinIndex]))
    EndSwitch

    [/autoit] [autoit][/autoit] [autoit]

    Next
    ConsoleWrite(StringFormat("\r\n"))
    EndFunc ;==>_Main

    [/autoit]
  • Natürlich sollte man zuerst den Algorithmus an sich verbessern. Wie kommt man eigentlich darauf? Bzw. wo gibt es Lektüre zu diesem Thema? Gibt's da spezielle Begriffe zu um sich das eine oder andere selber zu googeln?

    Die erste Regel ist, den Code zu profilen!
    In AutoIt haben wir dahingehend etwas gelitten, mehr wie einige Timings bekommt man nicht aus dem ausgeführten Code heraus...
    In anderen Programmiersprachen gibt es Profiling-Tools, welche Ergebnisse bis zum Erbrechen (Anzahl Prozessortakte) bereitstellen!
    Dabei sollte man beachten, dass es nichts nutzt, denjenigen Programmteil zu optimieren, der nur 5% der Gesamtlaufzeit beansprucht!
    Also so die Timings setzen, dass man den Codeteil findet, welcher am öftesten/meisten angesprungen wird!

    Die ersten Schritte wurden ja schon in den Beispielscripten gezeigt. Loop/Funktion ausstoppen, schneller = besser. Fertig!
    Aber wie kommt man zu einem "schnelleren" Algorithmus? Naja, kurz gesagt wurde so gut wie jedes EDV-technische Problem schon bis zum Abwinken behandelt :D
    Entweder man benutzt hochoptimierte Bibliotheken, oder man sucht in den einschlägigen Foren oder Webseiten <<LINK>>
    Aber auch einfaches analysieren bringt oft schon erstaunliche Ergebnisse.
    - Ist der Code zu parallelisieren, kann man ihn in mehrere "Threads" aufteilen?
    - Gibt es mathematische Umformungen, um Berechnungen "anders" durchzuführen?
    - Muss jede Funktion (UDF) unbedingt mit sämtlichen Errorhandlings ausgestattet sein?
    - Habe ich den Vergleich zum unoptimierten Code? Wie viel schneller ist der optimierte Code?
    - Wie viel schneller sind vergleichbare Programme? Lohnt es sich, Stunden/Tage/Wochen aufzuwenden um in einem Programm welches 2x am Tag läuft, 10 Sekunden einzusparen?

    Und man sollte sich fragen, wieso ein Prozessor, welcher heutzutage mit >4 Cores und 4,5Ghz Takt daherkommt, per "Sleeps" dahin gebracht wird, mit 1-2% Last zu laufen...
    Mir ist ehrlich gesagt lieber, der Prozessor hat so viel zu tun, dass mein Profiling-Tool kein einziges Mal den Tiefschlaf registriert! Erst wenn das Ding richtig glüht, wird es auch ausgenutzt! Einen Ferrari kauft man auch nicht, um 3x im Jahr im 2. Gang zum Einkaufen zu fahren....
    GuiGetMsg() ist so ein Kandidat, der in einer geschwindigkeitsoptimierten Schleife NICHTS zu suchen hat!

    ciao
    Andy


    "Schlechtes Benehmen halten die Leute doch nur deswegen für eine Art Vorrecht, weil keiner ihnen aufs Maul haut." Klaus Kinski
    "Hint: Write comments after each line. So you can (better) see what your program does and what it not does. And we can see what you're thinking what your program does and we can point to the missunderstandings." A-Jay

    Wie man Fragen richtig stellt... Tutorial: Wie man Script-Fehler findet und beseitigt...X-Y-Problem

    2 Mal editiert, zuletzt von Andy (11. Dezember 2014 um 21:01)

  • Hy,

    Ich habe mir den Thread mehrfach durchgelesen! Erstmal große Klasse das diese Infos zusammengetragen wurden.

    Ich habe mir das Programmieren vor ewigkeiten selber beigebracht, und meinen Skill ständig weiterentwickelt. Nur fällt mir in den letzten Jahren immer mehr auf, das ich mit den ganzen Bezeichnungen gar nichts anfangen kann... Da wirst du von jemanden gefragt ob du mit der Technik XXY arbeitest - und ich gucke dumm aus der Wäsche, weil ich keinen Schimmer habe was denn gemeint ist. Bis ich vor dem Rechner sitze, und total unbewusst 80% der hier vorgetragenen Techniken seit Jahren anwende - ohne das eigentlich zu Wissen...

    Muss mich umbedingt mehr mit der Theorie auseinander setzen...

    Grüsse!

    P.S:
    Andy hat es bereits angesprochen, Parallelisierung und auch Multi -Threading oder -Processing sind in der Optimierung der letzte große Schritt. Damit kann man, gerade Algoryhtmen, bis zum Faktor 3 bis 4 beschleunigen OHNE große Eingriffe im Algo selber durchführen zu müssen.

  • Warum reden hier alle von Parallelisierung?
    Es ist in nativem AutoIt nicht möglich (zumindestens kein Threading).

    Damit kann man, gerade Algoryhtmen, bis zum Faktor 3 bis 4 beschleunigen OHNE große Eingriffe im Algo selber durchführen zu müssen.

    Und dann wundern wenn dauernd falsche Ergebnisse zurückkommen...
    Parallelisierung ist selten so einfach wie die meisten denken.
    Einfach mal ein OpenMP-Tutorial durchlesen und seine Schleife parallelisieren führt selten zu einer linearen Skalierung mit den Threads.
    Optimierung auf paralleler Ebene ist eben nochmal ein ganz anderer Schuh.

    In anderen Fällen stimmt das Ergebnis auf einmal nicht mehr.
    Man sollte niemanden leichtfertig den Tipp geben, dass er seine Programme mit Parallelisierung mal ganz einfach um mehrere Vielfache beschleunigen kann ohne ihm vorher die Begriffe race condition, lost update oder Cache-Kohärenz zu erklären...

    Da es vorhin schon angesprochen wurde möchte ich es auch nochmal aufgreifen.
    Wenn man seine Programme optimieren will setzt man nicht einfach irgendwelche Tricks in seinem Programm um, sondern man beginnt mit einem Profiling (und zieht das den gesamten Optimierungsprozess auch durch).
    Die Teile, welche als relevant detektiert wurden, werden dann erst einmal auf Algorithmenebene optimiert bevor man die hier vorgestellten Tipps anwendet.
    Aber einfach nach dem Gießkannenprinzip alle Performancetipps auf das Skript verteilen hat mit Optimierung nicht wirklich viel zu tun.

    2 Mal editiert, zuletzt von AspirinJunkie (13. Dezember 2014 um 11:47)

  • Optimierung auf paralleler Ebene ist eben nochmal ein ganz anderer Schuh.

    wohl wahr...
    Das ist auch mit ein Grund, wieso diese Technik, obwohl seit Urzeiten ( >20 Jahren ) möglich, kaum verwendet wird! Es ist nämlich eine große Portion KnowHow und Skill vonnöten...
    Ich behaupte einfach mal, dass 99% der "Programmierer" schon mit dem Setzen der "richtigen" Compilerflags völlig überfordert sind, bzw. diese Art der Optimierung überhaupt nicht verstehen und auch nicht für nötig halten!
    Absolut gesehen rennt die "richtige" Programmierung und Anwendung der in Software verwendbaren möglichen Technik der Hardwareentwicklung 10 Jahre hinterher.
    Wieso ein Programm beschleunigen, wenn man für einige Hunderter einen Rechner kaufen kann, der bis auf eine Handvoll Hardcore-Spiele sowieso jegliches Programm schon im Schlafzustand des Prozessorss mit völlig ausreichender Geschwindigkeit ausführt?!
    Ich rede hier nicht von Forschungseinrichtungen und Hochschulen, welche dankbar sind für jedes kleine Quentchen Hardware-Power. DORT kostet Rechenpower richtig Geld, und genau dort wird bis auf den letzten Prozessortakt Software optimiert. Genau dort finden sich aber auch die Leute mit dem Skill und von dort kommen auch die hochoptimierten Bibliotheken ;)

    • Offizieller Beitrag

    Nachdem ich jetzt alle Beispiele durchprobiert habe, bin ich doch etwas überrascht bezüglich der Ausführungsgeschwindigkeit im X64-Modus.
    Wurde da mittlerweile so viel optimiert bei AutoIt (v3.3.12.0)?
    Ich meine mich erinnern zu können, dass es bei älteren Versionen keinen so gravierenden Unterschied gab, bzw. dass der X64-Modus sogar langsamer war.
    Könnt ihr das bestätigen oder läuft bei mir irgendwas im Hintergrund, was zu diesen Unterschieden führt. Hier mal die Ergebnisse mit dem Test von GtaSpider aus Post#13:

    Spoiler anzeigen


    >Result with AutoIt 3.3.12.0 x86.
    >CPU: AMD Phenom(tm) II X4 940 Processor X64
    >RAM: 5.19/8.00 GB
    >OS: WIN_7 (7601) X64
    >Parameter: 100
    +Func For (1) needs for 4095 runs 1482.29 ms (Average: 0.3620 ms) --> 1.0x
    -Func While (2) needs for 4095 runs 1783.99 ms (Average: 0.4357 ms) --> 1.2x
    !Func Do (3) needs for 4095 runs 1801.57 ms (Average: 0.4399 ms) --> 1.2x

    >Result with AutoIt 3.3.12.0 x64.
    >CPU: AMD Phenom(tm) II X4 940 Processor X64
    >RAM: 5.19/8.00 GB
    >OS: WIN_7 (7601) X64
    >Parameter: 100
    +Func For (1) needs for 4095 runs 951.56 ms (Average: 0.2324 ms) --> 1.0x
    -Func While (2) needs for 4095 runs 1169.11 ms (Average: 0.2855 ms) --> 1.2x
    !Func Do (3) needs for 4095 runs 1183.06 ms (Average: 0.2889 ms) --> 1.2x

  • Hier mal meine Daten für die NSA:

    Spoiler anzeigen

    >Result with AutoIt 3.3.12.0 x86.
    >CPU: Intel(R) Core(TM) i5-2450M CPU @ 2.50GHz X64
    >RAM: 5.42/7.98 GB
    >OS: WIN_7 (7601) X64
    >Parameter: 100
    +Func For (1) needs for 32767 runs 7935.01 ms (Average: 0.2422 ms) --> 1.0x
    !Func While (2) needs for 32767 runs 9730.47 ms (Average: 0.2970 ms) --> 1.2x
    -Func Do (3) needs for 32767 runs 9682.48 ms (Average: 0.2955 ms) --> 1.2x

    >Result with AutoIt 3.3.12.0 x64.
    >CPU: Intel(R) Core(TM) i5-2450M CPU @ 2.50GHz X64
    >RAM: 5.42/7.98 GB
    >OS: WIN_7 (7601) X64
    >Parameter: 100
    +Func For (1) needs for 32767 runs 6842.51 ms (Average: 0.2088 ms) --> 1.0x
    -Func While (2) needs for 32767 runs 8460.29 ms (Average: 0.2582 ms) --> 1.2x
    !Func Do (3) needs for 32767 runs 8712.79 ms (Average: 0.2659 ms) --> 1.3x