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.

1) Der Stack startet am Ende des Arbeitsspeichers und wächst Richtung Anfang. Die Angabe avr.Stack legt fest wieviel Bytes der Stack belegt
2) Unbelegte Objektvariablen wie String oder MemoryBlock sind dann nil wenn ihnen noch kein Objekt (MemoryBlock) zugewiesen wurde