Speicherverwaltung
Für die Verwaltung des Arbeitsspeichers (SRAM) steht in Luna eine Speicherverwaltung zur Verfügung. Diese Speicherverwaltung erlaubt das dynamische Unterteilen des Arbeitsspeichers in einzelne Blöcke verschiedener Größe. Ein solcher Block nennt sich in Luna MemoryBlock und besitzt Objekteigenschaften.
Bei der in Luna implementierten Speicherverwaltung handelt es sich um eine auf hohe Effizienz ausgelegte, integrierte, automatische, generationelle Speicherbereinigung (Garbage Collection). In der Praxis ist die Lebensdauer von Objekten meist sehr unterschiedlich. Auf der einen Seite existieren Objekte, die die gesamte Laufzeit der Applikation überleben. Auf der anderen Seite gibt es eine große Menge von Objekten, die nur temporär für die Durchführung einer einzelnen Aufgabe benötigt werden. Mit jeder Anwendung des Freigabe-Algorithmus werden langlebige Objekte in eine höhere Generation verschoben. Der Vorteil liegt darin, dass die Speicherbereinigung für niedrige Generationen häufiger und schneller durchgeführt werden kann, da nur ein Teil der Objekte verschoben und deren Zeiger verändert werden müssen. Höhere Generationen enthalten mit hoher Wahrscheinlichkeit nur lebende (bzw. sehr wenige tote) Objekte und müssen deshalb seltener bereinigt werden. Die Besonderheit des in Luna implementierten Algorithmus ist, dass er keine Zähler für die Lebensdauer der einzelnen Objekte benötigt und alle Objekte Rückwärtsreferenzen aufweisen. Die Rückwärtsreferenzen erlauben es „tote“ Speicher-Objekte und fehlerhafte Objektvariablen zu erkennen. Zugriffe auf ungültige Objekte können mittels einer Exception erkannt werden.
Genutzt wird die Speicherverwaltung in Programmen immer dann, wenn instrinsische Variablen mit Verweisen auf dynamischen Speicher genutzt werden, wie z.B. string oder MemoryBlock. Die Speicherverwaltung kann jedoch aus technischen Gründen nur sinnvoll genutzt werden, wenn mehr als 64 Bytes Arbeitsspeicher frei verfügbar sind.
Ist die Speicherverwaltung nicht nutzbar, sind Stringvariablen und darauf zugreifende Stringfunktionen, sowie MemoryBlocks im Programm zu vermeiden.
Typische Speicheraufteilung in Luna
Aufbau eines Speicherblocks
Da die Speicherverwaltung wissen muss wo und wieviel Speicher belegt ist, benötigt jeder Speicherblock (MemoryBlock) einige Bytes um Verwaltungsdaten zu speichern (Header).
Ein MemoryBlock besteht aus einem Header (5 bytes) und den Nutzdaten:
Name | Wert | Typ | Beschreibung |
---|---|---|---|
Magic | 0xAA | byte | Erkennungsbyte |
Length | Number | word | Anzahl der Nutzdaten in Bytes |
Ref | Address | word | Rückreferenz auf die Objekt-Variable mit welcher der Speicherblock aktuell verknüpft ist oder Null. |
Data | Nutzdaten |
Ist zu wenig Arbeitsspeicher vorhanden, kann der Speicher selbst verwaltet werden. Hierfür steht der direkte Zugriff auf den gesamten Arbeitsspeicher durch das Objekt sram zur Verfügung.
Veranschaulichung
Man nehme an, es wurden mehrere Stringvariablen dimensioniert:
dim datum,zeit as string
Bei der Zuweisung, oder bei der Verwendung von Stringfunktionen, wird das Ergebnis in einem MemoryBlock gespeichert und die Startadresse der Daten im MemoryBlock als Zeiger (Pointer) der entsprechenden Variable zugewiesen. Es wird hierbei absichtlich nicht die eigentliche Startadresse des MemoryBlocks als Zeiger zugewiesen, sondern der Beginn der Daten. Andernfalls müsste bei jedem Zugriff die Größe des Headers hinzugerechnet werden, was ineffizienter wäre.
Nehmen wir ebenfalls an, dass der Arbeitsspeicher mit der Adresse 100 (dezimal) beginnt. Die Variablen datum und zeit sind 16-Bit-Zeiger und belegen damit jeweils 2 Byte im Arbeitsspeicher:
Speicher → | |||
---|---|---|---|
100 | 101 | 102 | 103 |
Variable datum | Variable zeit | ||
0 (nil) | 0 (nil) |
Hinter diesen Variablen beginnt der dynamische (frei verfügbare) Arbeitsspeicher und endet beim Stack-Ende 1). Wurde den Stringvariablen noch kein Wert zugewiesen, haben sie den Wert nil 2).
Durch Zuweisung eines Wertes, wird ein MemoryBlock im Arbeitsspeicher erzeugt:
dim datum,zeit as string datum="13.11.1981"
Im Speicher sieht es nun folgendermaßen aus:
Speicher → | |||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 |
109 | 0 (nil) | 0xAA | 11 | 100 | 10 | '1' | '3' | '.' | '1' | '1' | '.' | '1' | '9' | '8' | '1' | ||||
Variable datum | Variable zeit | Header MemoryBlock | Daten MemoryBlock |
Wie man erkennen kann, ist die Variable zeit unbelegt und die Variable datum hat als Wert nun die Anfangsadresse der Daten vom MemoryBlock. Die Daten der Zeichenkette sind wiederum als Pascal-String abgelegt, damit verarbeitende Funktionen die Länge der Zeichenkette ermitteln können. Dies ist wichtig, da nur die unsichtbar im Hintergrund agierende Speicherverwaltung selbst auf den Header des MemoryBlocks zugreift und zugreifen darf.
Weiterhin ist zu erkennen, dass im Header des MemoryBlocks nun die Adresse der Variable gespeichert ist. Variable und MemoryBlock verweisen also aufeinander. Dies ist wichtig, da bei einer Freigabe eines MemoryBlocks und dem damit verbundenem Aufräumen des Speichers (defragmentieren/komprimieren) sich die Adresse der nachfolgend noch vorhandenen MemoryBlöcke verändert (sie rücken auf). Die Speicherverwaltung passt dann alle Variablen an, die einen Verweis (Zeiger) auf einen MemoryBlock beinhalten.
Vor- und Nachteile
Jede Art der Speicherverwaltung besitzt ihre Vor- und Nachteile. Ist keine Speicherverwaltung vorhanden, muss sich der Programmierer penibel selbst darum kümmern den Speicher sinnvoll aufzuteilen. Dies schließt den Nachteil mit ein, dass beispielsweise Strings mit einer festen Länge dimensioniert werden müssen (statische Strings). Kann also ein String innerhalb des Programms bis zu 60 Bytes groß werden, muss von Anfang an dieser Speicher dafür reserviert sein, auch wenn im überwiegenden Teil des Programms nur z.Bsp. 10 Bytes benötigt werden. Vorteilig ist diese Variante dann, wenn nur wenige und kleine Stringoperationen erfolgen und wenig Arbeitsspeicher zur Verfügung steht.
Eine Speicherverwaltung ist dann effizienter und z.T. trotz der zusätzlichen Verwaltungsdaten gar speicherplatzsparender, wenn es sich um mehrere zu verwaltende Strings handelt und Daten in eigenen Speicherbereichen verwaltet werden müssen. Zudem ist es einfacher dynamische Daten zuzuweisen. Bei wenig Speicher (weniger als 128 Bytes freiem Speicher) ist sie wiederum ineffizient durch die entstehenden Verwaltungsdaten.
Luna versucht beides dadurch zu kombinieren, indem zusätzlich die Möglichkeit besteht durch Strukturen statische Strings bzw. Datenblöcke anlegen zu können. Beschränkt man sich auf Ein- und Ausgabe und vermeidet Stringfunktionen wie Left(), Right() oder Mid() usw., oder bildet sie selbst für die eigenen Speicherstrukturen, vereint dies beide Varianten der Verwaltung des Arbeitsspeichers.