Verständnisfrage zu "Local vs Global" in Bezug auf Geschwindigkeit und Ressourcenbelegung

  • Aloha und nen schönen Feiertag,

    ich hab da mal ne allgemeine Frage. Wie ihr im beigefügten Script sehen könnt, belegen die embedded Icons, die als String in einem Dictionary abgelegt sind, ganz schön was an Platz.

    In meinem Beispiel würde bei jeder Tray-Icon-Änderung die _fetchIcon() UDF aufgerufen werden, in welcher sämtliche Icons Local deklariert sind - für das Beispiel hier nur 2 Icons, im eigentlichen Scipt sind das dann aber bis zu 20 oder mehr. Diese liefert dann den gewünschten String zurück, der im Anschluss dekodiert und als Icon gesetzt werden kann.

    Das bedeutet natürlich, dass jedes Mal alle Icons frisch in den Speicher geladen werden müssen, was natürlich auch Zeit in Anspruch nimmt.

    Daher meine Frage: sollte man die Icons lieber Global speichern und zu Beginn des Skripts einlesen? Würde das einen Geschwindigkeitsvorteil bringen? Vor allem, wenn die String-Variablen, wie in diesem Beispiel, eine ganz schöne Länge haben.

    • Offizieller Beitrag

    Du machst einen gravierenden Fehler!

    Mit _GDIPlus_BitmapCreateFromMemory erstellst Du bei jedem Aufruf eine neue Bitmap. Diese werden aber beim verlassen der Funktion nicht wieder freigegeben. Das führt zu einem Speicherleck.

    Besser einmal (global) die Icons erstellen und dann diese nutzen und bei Programmende wieder freigeben.

    Das behebt nicht nur das Speicherleck, sondern ist auch schneller.

  • Oscar

    Das hab ich verbrochen. Das ist das Beispiel aus dem File to Base64 String Code Generator-Thread im engl. Forum.

    Slevin

    Pack _GDIPlus_BitmapDispose und _WinAPI_DestroyIcon mit in die _SetTrayIcon-Funktion.

    So wie du die Funktion _fetchIcon() nutzt, wird tatsächlich jedes Mal von neuem jedes Icon in eine Variable gespeichert und und im Anschluss nur das einzige erfragte Icon zurückgegeben. Das ist wirklich nicht so toll.

    Wenn du es unbedingt so machen willst, wäre es tatsächlich besser, du würdest gar keine Funktion aufrufen sondern dein Dictionary mit allen Icons global deklarieren. Am besten in einer eigenen au3, die du dann nur in dein Skript inkludierst. So verbraucht dein Skript zwar mehr Speicher (eben den für alle Icons, die du vielleicht in der Laufzeit mal aufrufst), aber es geht. Und so viel Speicher werden die Icons auch nicht belegen, oder reden wir wirklich von irre vielen Icons?

    Ansonsten hätte ich auf das Dictionary verzichtet und die Icons in Funktionen gepackt, die lock oder live heißen. Ist aber vllt. Geschmackssache.

    Grüße autoiter

    • Offizieller Beitrag

    Statt des Dictionarys könnte man auch Arrays nehmen und die Indizes mit Variablen (Enum) benennen:

  • Es ginge doch auch so

    Grüße autoiter

  • Du machst einen gravierenden Fehler!

    Mit _GDIPlus_BitmapCreateFromMemory erstellst Du bei jedem Aufruf eine neue Bitmap. Diese werden aber beim verlassen der Funktion nicht wieder freigegeben. Das führt zu einem Speicherleck.

    Besser einmal (global) die Icons erstellen und dann diese nutzen und bei Programmende wieder freigeben.

    Das behebt nicht nur das Speicherleck, sondern ist auch schneller.

    Also entweder wie autoiter meinte, die _setTrayIcon() UDF so gestalten:

    AutoIt
    Func _setTrayIcon($icon)
        $hBmp = _GDIPlus_BitmapCreateFromMemory(_WinAPI_Base64Decode(_fetchIcon($icon)))    ;load ico and convert it to a GDI+ bitmap
        $hIcon = _GDIPlus_HICONCreateFromBitmap($hBmp)                    ;convert bitmap to HIcon
        _WinAPI_TraySetHIcon($hIcon)
        _GDIPlus_BitmapDispose($hBmp)
        _WinAPI_DestroyIcon($hIcon)
    EndFunc

    Oder aber jedes Icon global fertig deklarieren, das würde dann jeweils in etwa so aussehen:

    AutoIt
    Local $iconBaselock = 'AAABAAEAEBAAAAEACABoBQAAFgAAACgAAAAQAAAAIAAAAAEACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjI7EAIiKwACIirwAhIa4AICCtAB8frAAeHqoAHR2pABwcpwAbG6UAGRmkABgYogAXF6AAFhaeABQUnAATE5oAEhKZABAQlwAPD5UA8fHxAA0NkwAMDJEACwuPAAoKjQAICIwABweKAAYGiAAFBYcABASFAAMDhAACAoMAAQGCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADxAREhQVFhcYGRobHB0eHw4PEBESFBUWFxgZGhscHR4NDg8QERIUFRYXGBkaGxwdDA0ODxAREhQVFhcYGRobHAsMDQ4PEBETExUWFxgZGhsKCwwNDg8QExMUFRYXGBkaCQoLDA0ODxMTEhQVFhcYGQgJCgsMDQ4TExESFBUWFxgHCAkKCwwNExMQERIUFRYXBgcICQoLDBMTDxAREhQVFgUGBwgJCgsTEw4PEBESFBUEBQYHCAkKExMNDg8QERIUAwQFBgcICQoLDA0ODxAREgIDBAUGBwgJCgsMDQ4PEBEBAgMEBQYHCAkKCwwNDg8QAAECAwQFBgcICQoLDA0ODwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA='
    
    Global $iconBase = ObjCreate("Scripting.Dictionary")
    $iconBase("lock") = GDIPlus_HICONCreateFromBitmap(_GDIPlus_BitmapCreateFromMemory(_WinAPI_Base64Decode($iconBaselock)))
    
    Func _setTrayIcon($icon)
        _WinAPI_TraySetHIcon($icon)
    EndFunc
    
    _setTrayIcon("lock")

    Wenn ich das jetzt richtig verstanden habe, dann wird bei der ersten Variante jedes Mal das Icon aufs neue kreiert, aber der für das Erstellen des Icons benötigte Bitmap-Speicher wieder freigegeben.

    Bei der zweiten Variante werden alle Icons fertig kreiert und bleiben im Speicher, sind daher schneller ansprechbar, und es gibt auch kein Speicherleck - was insgesamt wahrscheinlich die intelligentere Variante ist, sofern sich das ganze in Grenzen hält (wie gesagt, so um die 20 Icons im aktuellen Fall)

    Das mit dem Speicherleck wäre natürlich gar nicht gut gewesen - DANKE für die Warnung

  • Ich habe hier mal was gebastelt...

  • Bitnugger, das ist wirklich gut, danke :)

    Drei Fragen hätte ich da noch zu deiner Version:

    1. Hat das separate Deklarieren und Schließen von Global $g_hDll = DllOpen("shell32.dll") einen Vorteil?

    2. Wieso hast du beim Setzen des Tray-Icons ein Error-Handling angefügt, würden eventuelle Fehlermöglichkeiten nicht schon vorab ausgeschlossen werden?

    3. Kann man einem Errorhandling auch eine UDF zuweisen, in etwa so: If @error Then _exitFunction() ConsoleWrite("@@ Debug line" & ...

  • 1.) Globale Variablen sollten nicht innerhalb von Funktionen deklariert werden. Genauso schlimm, finde ich, wenn sich globale Variablen nicht von lokalen unterscheiden lassen, deshalb beginnen meine globalen immer mit $g_. Ich achte aber sehr darauf, dass ich möglichst mit lokalen Variablen arbeite, zumal das bei sehr großen Scripten die Fehlerträchtigkeit deutlich minimiert. Das DllOpen ergibt jetzt bei deinen 20-30 Icons nicht wirklich einen Mehrwert, aber wirklich wichtig wird es, wenn die DLL während der Laufzeit immer wieder angesprochen wird, z. B. mit der Funktion _IsPressed. Doch es kostet ja nichts.

    2.) Die habe ich drin gelassen, damit man sehen kann, wie ich die Sachen in der Debug-Phase angehe. Später würde ich dann einfach ein Return SetError(1, 0, False) zurückgeben oder kurz eine Message einblenden.

    3.) So ähnlich... wie genau man das angeht, ist aber wohl eine Sache des persöhnlichen Geschmacks, wobei ich dann sehr darauf achte, dass die Stelle des Fehlers mithilfe des Errors leicht auffindbar ist... also nicht immer SetError( 1, 0... oder du machstes mit _Assert.

    _Assert ( $sCondition [, $bExit = True [, $iCode = 0x7FFFFFFF [, $sLine = @ScriptLineNumber]]] )

    AutoIt
    ; ...
    If @error Then _ErrorExit(77749, 2, '_WinAPI_DestroyIcon($hIcon)')
    ; ...
    
    Func _ErrorExit($iError, $iExtended, $sErrMsg, $iLineNumer = @ScriptLineNumber)
        If $iError Then ConsoleWrite(StringFormat('Debug line (%5i)  #Error: %i \t Extended: %i \t #ErrMsg: %s\n', $iLineNumer, $iError, $iExtended, $sErrMsg))
        Exit $iError
    EndFunc

    PS: In deinem zweiten Script Teil 2, was passiert denn da mit den Bitmaps? Das ist nämlich auch wieder ein Speicherleck! 8o

    Einmal editiert, zuletzt von Bitnugger (31. Mai 2018 um 13:30)

  • PS: In deinem zweiten Script Teil 2, was passiert denn da mit den Bitmaps? Das ist nämlich auch wieder ein Speicherleck! 8o

    Stimmt, aber nur ein bedingtes, weil es nur maximal einmal mit allen Icons befüllt wird ;) (wenn ich das jetzt halbwegs korrekt verstanden habe)

  • Ja leider, ich muss mich da einfach noch mehr in die Materie einlesen, von daher bin auch dankbar für jeden Hinweis und all die Hilfe, die ich hier erhalte.

    Ich bastel für gewöhnlich einfach so vor mich hin, meist Sachen in VBS. Aber memory-management hatte ich nie nötig zu beachten, da fehlts mir also einfach an Wissen.

    Aber du hast recht, ich werde das Script in Zukunft sicher auch für andere Zwecke umbauen, und dann wäre sowas nicht gerade smooth...