Sven's CA-Visual Objects - Seite

23.11.2012

eMail

eMail

Übersicht | Vorheriger | Nächster

Garbage Collector

Einen hervorragenden und weitgehend umfassenden Artikel über den GC hat Uwe Holz im Jahre 1999 in Heft 3 der SDT veröffentlicht. Darüber hinaus möchte ich hier auf zwei Dinge näher eingehen.

  1. Wie VO den dynamischen Speicher verwaltet und wie man dieses Verhalten beeinflussen kann.
  2. Wie RegisterKid() zu einem Problem werden kann.
  3. Der Registry-Eintrag WipeDynSpace.

 

zu 1.)

Zuerst definiert der RunTime Registry-Eintrag "MaxDynSpace" die maximale Anzahl Bytes dynamischen Speichers, den sich die VO-Runtime reservieren darf. Die tatsächliche Größe des dynamischen Speichers (also die Größe, die VO von Windows anfordert und die anderen Anwendungen nicht mehr zur Verfügung stehen) verwaltet VO normalerweise auch dynamisch in Schritten von 64KB. Benötigt VO mehr dynamischen Speicher, so wird der Speicherbereich entsprechend erhöht. Dies geschieht maximal bis zu der Grenze die in MaxDynSpace angegeben ist. Der Default-Wert betrag 16MB. Hat VO den Speicherbereich nach Anforderung erhöht, so wird dieser aus Geschwindigkeitsgründen nicht mehr freigegeben, auch wenn er eigentlich nicht mehr benötigt würde. Möchte man den nicht mehr benötigten Speicherbereich Windows zurückgeben, so ist dies mit der Funktion DynShrink() möglich. Dies funktioniert allerdings nur dann, wenn zu keinem Zeitpunkt vorher die Funktion DynSize() aufgerufen wurde.

Hierzu ein Beispiel:
Wir nehmen an, MaxDynSpace steht auf seinen Default-Wert nämlich 16MB. Nach dem Programmstart ist der dynamische Speicher genau eine Speicherseite - 64KB - groß. Bei jeder Speicheranforderung von Objekten, die dynamischen Speicher benötigen wie Strings, Floats, Objekte etc. wird geprüft ob noch soviel Speicher vorhanden ist. Ist dies nicht der Fall, so wird der Garbage-Collector kurz GC aufgerufen um eventuell Platz zu schaffen. Ist nach dem GC-Lauf immer noch nicht genügend Speicher vorhanden, so wird die Größe des dynamischen Speichers um die benötigten Speicherseiten erhöht. Angenommen wir öffnen ein Fenster welches 100KB dynamischen Speichers benötigt, so würde VO den Speicherbereich um 2 Speicherseiten vergrößern also auf 192KB. Wird das Fenster wieder geschlossen, bleibt diese erreichte Größe erst einmal erhalten. Erst ein Aufruf von DynShrink() würde in diesem Fall den dynamischen Speicherbereich wieder auf 64KB schrumpfen.

Neben dieser weitestgehend automatischen Verwaltung des dynamischen Speichers. gibt es auch die Möglichkeit selbst die Größe des dynamischen Speicherbereichs mit Hilfe der Funktion DynSize() zu bestimmen. DynSize() gibt die Anzahl an Speicherseiten (64KB-Blöcke) an, die VO sofort in vollem Umfang zur Verfügung stehen sollen. Dies ist in all den Fällen sinnvoll, in denen diese Automatik zu träge wird.
In großen Anwendungsprogrammen mit hohem Bedarf an dynamischen Speicher kann folgende Situation eintreten. Der dynamische Speicher hat bereits eine Größe von sagen wir 2MB erreicht und hat nur noch eine geringe Reserve. Nun soll ein Fenster mit z. B. 200 Controls geöffnet werden. Dabei werden Hunderte dynamische Speicherobjekte erzeugt. Im schlimmsten Fall triggert nun jedes dieser Objekte einerseits den GC und zusätzlich die automatische Speicheranforderung an Windows für zusätzlichen dynamischen Speicher. Es verwundert nun nicht mehr, dass solche Fenster in dieser Situation quälend langsam geöffnet werden. Mit Hilfe der Funktion DynSize() gibt es nun prinzipiell zwei Möglichkeiten Abhilfe zu schaffen.

  • 1. Man Reserviert beim Programmstart mit DynSize() auf einmal so viel dynamischen Speicher, wie ihn das Programm maximal benötigt. Benötigt das Programm maximal z.B. 8 MB dynamischen Speicher, so würde man DynSize(128) eingeben. Diese 8MB stehen dem Windowssystem aber für die Dauer der Programmausführung nicht mehr zur Verfügung. Außerdem scheint VO 2.5b-3 noch einen Bug zu haben, da in einigen Fällen diese Vorgehensweise zu unerklärlichen GPF's führt, wenn dann auch diese in unserem Beispiel eingestellte Grenze von 8MB überschritten wird, VO also quasi oberhalb dieser 8MB wieder in den automatischen Betrieb wechselt.
  • 2. Die zweite elegantere Möglichkeit ist die, vor dem Aufruf großer Fenster deren Speicherbedarf auf einmal zu reservieren und beim Zerstören dieser Fenster diesen wieder frei zu geben. Glücklicherweise lässt sich mit DynSize() der Speicherbereich auch wieder verkleinern, vorausgesetzt der GC konnte wieder genügend Platz schaffen. Wie weiter oben schon erwähnt funktioniert ja DynShrink() nicht mehr sobald man die Funktion DynSize() eingesetzt hat. Damit das Ganze einfach zu handhaben ist, habe ich eine Funktion DynCheck() geschrieben, die diese Art der Speicherverwaltung leistet.:

    FUNCTION DynCheck(dwBytesNeeded AS DWORD) AS LOGIC
    STATIC dwMaxPages AS DWORD
    LOCAL  dwDynSize  AS DWORD
    LOCAL dwDynNeeded AS DWORD

    CollectForced()

    dwDynSize   := DynInfoSize()
    dwDynNeeded := ((dwBytesNeeded + DynInfoUsed()) /
    0x10000l) + 1l

    IF dwDynNeeded <= dwDynSize
      
    IF dwDynNeeded < dwDynSize
          DynSize(dwDynNeeded)
      
    ENDIF
      
    RETURN TRUE
    ENDIF


    IF dwMaxPages = 0
       dwMaxPages := QueryRTRegInt(
    "RunTime","MaxDynSpace")
      
    IF dwMaxPages = 0
          dwMaxPages :=
    256
      
    ENDIF
    ENDIF


    IF dwDynNeeded <= dwMaxPages
       DynSize(dwDynNeeded)
      
    RETURN TRUE
    ENDIF

    RETURN FALSE


    Man ruft DynCheck() mit der Anzahl der für das Fenster maximal benötigten Bytes in der PreInit() Methode auf. Und in der Destroy() Methode ruft man DynCheck() wieder auf, nur diesmal mit der Anzahl der Bytes dynamischen Speichers, den man noch sofort zur Verfügung haben möchte. DynCheck() reduziert dann automatisch wenn möglich den Dynamischen Speicher soweit, dass diese angegebene Anzahl noch mindestens zur Verfügung steht.

 

zu 2.)

RegisterKid() (siehe VO Hilfe) wird dazu verwendet Zeiger (Pointer) auf dynamische Speicherobjekte in statischem Speicher (über MemmAlloc() bereitgestellter Speicher) beim Garbage-Collector anzumelden. Der GC weiß dann, dass dort Zeiger auf dynamische Objekte vorhanden sind und aktualisiert automatisch dessen Werte nach einem GC-Lauf.

Der GC arbeitet sehr schnell und auch sehr intelligent. Er erkennt auch solche Zeiger als ungültig, die in anderen Objekten als Instanz-Variablen abgespeichert sind, die ihrerseits bereits ungültig sind. Das heißt der Zeiger auf das dynamische Objekt existiert noch, aber das Objekt dem der Zeiger gehört ist ungültig. Dadurch gewährleistet VO, das sich zwei Objekte nicht gegenseitig dadurch blockieren können, das sie jeweils einen Verweis des anderen Objekt gespeichert haben. Der GC wird beide Objekte löschen, obwohl ja innerhalb der Objekte noch Zeiger existieren, die aber durch die Ungültigkeit des Objektes in dem sie stehen selber ungültig werden.
Diese sehr intelligente Vorgehensweise funktioniert allerdings nur bei Objekten. Hat man hingegen einen Zeiger auf ein Objekt im statischen Speicher abgelegt, so muss man diesen mit RegisterKid registrieren, damit der Zeiger auch nach einem GC-Lauf noch stimmt. Solange dieser Zeiger auf das Objekt registriert ist kann der GC das Objekt niemals löschen auch wenn sonst nirgends mehr Referenzen auf dieses Objekt existieren.

Man muss deshalb bei RegisterKid() immer darauf achten, dass garantiert im Programm irgendwann ein zugehöriges UnRegisterKid() kommt, da sonst diese Objekte für immer im dynamischen Speicher bleiben, auch wenn sie schon lange ungültig sind.

 

zu 3.)

Der Registry-Eintrag WipeDynSpace ist eigentlich ein Debug-Feature. Normalerweise wird der frei gewordene Speicher beim Lauf des Garbage-Collectors nicht explizit gelöscht sondern es werden nur die Adressen der noch gültigen Objekte neu berechnet. Das Löschen ist eigentlich auch unnötig und würde deshalb nur Zeit kosten. WipeDynSpace veranlaßt den GC nun trotzdem den Speicher von ungültig gewordenen Objekten sofort durch Auffüllen mit 0x00 zu löschen. Dies hat den Vorteil, dass sich bestimmte Programmfehler sofort an der verursachenden Stelle im Programm durch in der Regel einen GPF (5333) bemerkbar machen.

Home | Kontakt | Impressum | ©2012 Ingenieur-Büro Dipl. Ing. Sven Ebert