Clause
← Gesamte Dokumentation

Übersetzt aus dem englischen Original · 1e3a39c8-dirty · 2026-10-26 · Auf Englisch lesen

Vertrag des Prozess-Heaps

Prozess-Heaps folgen dem klassischen ERTS-Design: ein zusammenhängender Heap-Block pro Prozess plus Überlauffragmente, selbstbeschreibende Zellen, große Binaries in geteilten Puffern außerhalb des Heaps und eine kopierende Speicherbereinigung, die nur an Safepoints läuft. Diese Notiz ist der Vertrag; runtime.md fasst die Host-API zusammen.

Wortlayout

Ein Term ist ein Zielwort (32 oder 64 Bits); die Kodierungen stehen in abi.md. Jeder Heap-Bereich ist eine Folge von Objekten, die ein Walker ab ihrem ersten Wort parst:

Keine Zelle braucht eine stärkere Ausrichtung als ein Wort. memory/heap_walk parst einen Bereich Zelle für Zelle, und ProcessHeap::verify prüft einen ganzen Heap. Zellen enthalten nur Wörter und Bytes, außer dem std::shared_ptr des Off-Heap-Binarys (unten), sodass eine Zelle durch Kopieren ihrer Wörter verschoben wird.

ArtWörter nach dem HeaderVerfolgte Wörter
Cons (kein Header)insgesamt 2 WörterKopf, Rest
tuplen Elementplätzealle
map2n Plätze: Schlüssel in exakter Termordnung, jeweils gefolgt von ihrem Wert; höchstens 32 Einträgealle
tree_mapAnzahl der Einträge, Bearbeitungswort, Wurzel-map_node: 3 Wörter (Map-Bäume)die Wurzel
map_nodeEinträge darunter, Bearbeitungswort, dann n Schlüssel und n Werte (ein Blatt) oder n Kinder (eine Verzweigung); nie ein TermSchlüssel und Werte oder Kinder
native_recordAdresse der RecordDefinition der Runtime, dann n Feldwerte in DefinitionsreihenfolgeWerte
fun_closureAdresse der FunDefinition der Runtime, dann n erfasste Werte (Funs)Werte
bignumVorzeichenwort, dann Betragsglieder (limbs), niederwertigstes zuerstkeine
floating8 Bytes: 1 Wort (64 Bit) oder 2 Wörter (32 Bit)keine
reference8 Bytes: die Referenznummer (Pids und Referenzen)keine
heap_binaryBitlänge, dann Datenbytes, auf Wörter aufgerundet (höchstens 64 Bytes)keine
refc_binaryBit-Offset, Bitlänge, das erste Byte des Puffers (von erzeugtem Code gelesen), Anhängeende und -grenze, std::shared_ptr (2 Wörter), Off-Heap-Verknüpfung: 8 Wörterkeine
sub_binaryBit-Offset in das Original, Bitlänge, das ursprüngliche heap_binary oder refc_binary (nie ein Sub-Binary): 3 Wörterdas Original
fillern ungenutzte Wörterkeine

Die Anzahl bei map ist in Wörtern angegeben (Einträge = Anzahl / 2). Pids und Ports sind Immediates und werden gegen die von der Runtime vergebenen Nummern zugelassen.

Off-Heap-Binaries

Ein Binary größer als 64 Bytes ist ein unveränderlicher Puffer, der außerhalb jedes Prozess-Heaps liegt und per Referenzzählung geteilt wird (BEAM ProcBin und Binary).

Wiederverwendung von Puffern

Ein freigegebener Puffer mit einer Zweierpotenz als Größe von mindestens 256 Bytes (den Kapazitäten anhängbarer binaries) bleibt bei dem Thread, der seine letzte Referenz verworfen hat, für dessen nächsten Puffer genau dieser Größe (wie Heap-Blöcke). Jeder Thread behält höchstens 32 Puffer und 8 MB; der Cache wird beim Ende des Threads freigegeben. Die Bytes eines wiederverwendeten Puffers hinter denen des neuen binary bleiben unverändert: Keine Sicht umfasst sie. Ohne dies bezogen Schleifen zum Anhängen an binaries für jeden vergrößerten Puffer frische Seiten (der C++-Heap gibt die Seiten großer freigegebener Blöcke zurück). Neue Puffer sind ByteBuffers (runtime/include/byte_buffer.hpp), die ihren Speicher ebenfalls nie nullen, sodass die Antwort eines Port-Treibers (eine vollständig gelesene Datei) einmal geschrieben und unverändert übernommen wird.

Handle-Referenzen

Eine Handle-Referenz ist eine Referenz, die zusätzlich einen Off-Heap-Puffer hält, so wie eine magische Referenz in OTP eine Ressource hält; atomics-Arrays leben darin (atomics und counters).

Map-Bäume

Eine Map mit mehr als 32 Einträgen (MAP_SMALL_MAP_LIMIT von OTP) ist ein 4-Wort-Stub tree_map, der die Wurzel eines persistenten B+-Baums benennt (terms/map_tree), dessen Knoten map_node-Zellen desselben Heaps sind, so wie OTP seine HAMT-Knoten auf dem Heap hält. Kleinere Maps bleiben map-Zellen.

Bereiche

Dimensionierung und Budget

Heap-Blöcke

Heap-Blöcke, Fragmente und alte Bereiche stammen aus memory/heap_blocks. Ein Block, den eine Bereinigung freigibt, bleibt für die nächsten Bereinigungen dieses Threads bei seinem Thread (so wie die Carrier von ERTS Speicher behalten): Ein neuer Block nimmt den kleinsten zwischengespeicherten Block, der mindestens seine Größe und höchstens das Doppelte davon hat, sonst frischen Speicher. Jeder Thread behält höchstens 8 Blöcke und 16M Wörter (128 MB auf 64-Bit-Zielen); Blöcke unter 4.096 Wörtern werden nicht behalten, da die Freilisten des C++-Heaps sie günstig bedienen. Ein freigegebener Block, der nicht hineinpasst, gibt zuerst die ältesten zwischengespeicherten Blöcke frei: Ein wachsender Heap gibt immer größere Blöcke frei, und ohne Verdrängung füllten die Blöcke seiner frühen, kleinen Bereinigungen den Cache, sodass eine lebende Datenmenge von Millionen Wörtern für jeden jungen Block, alten Bereich und vollständigen Durchlauf frische Seiten bezog. Ohne dies nahm der Zielbereich (to-space) jeder Bereinigung frische Seiten vom Betriebssystem und zahlte einen Seitenfehler pro berührter Seite, und das Freigeben eines großen Blocks gab seine Seiten zurück. Zwischengespeicherte Blöcke werden keinem Prozess und nicht dem runtime-weiten Konto angerechnet; ein Thread gibt seinen Cache frei, wenn er endet.

Runtime-Speicherlimit

Zulassung

Zeiger in einen Prozess-Heap werden nur vom Compiler und von der Runtime innerhalb dieses Prozesses erzeugt und benennen immer einen Objektanfang; es gibt keine inneren Zeiger zu erkennen. Die Zulassung (admission) ist eine Besitzprüfung für Wörter, die an einen Prozess zurückgegeben werden. Term::from_word(word, context) / TermAccess::admit prüfen zuerst, ob der Speicher noch lebt (expired_context); Atome und Pids/Ports müssen dieser Runtime gehören (feste Atome werden geteilt). Für zusammengesetzte Wörter:

  1. Die Adresse ist wortausgerichtet innerhalb eines der Bereiche des Prozesses, unterhalb seines top: Zuerst wird der Heap-Block geprüft, dann die nach Adresse sortierten Fragmente, dann die Literalbereiche (Bereiche). Adressen außerhalb dieser Bereiche scheitern ohne Ladezugriff; alte Wörter an wiederverwendeten Adressen werden nicht erkannt.
  2. Ein geboxtes Wort benennt einen Header einer zugelassenen Art (keine Füllzelle); ein Listenwort benennt eine Cons-Zelle (ein Wort, das kein Header ist).

Nach der Zulassung dekodiert TermAccess::object Art, Anzahl und Nutzdaten über trusted_cell ohne Heap-Abfrage; child umschließt das gespeicherte Wort und validate prüft nur die Darstellung. Fabriken lassen Eingaben weiterhin über ProcessHeap::retain erneut zu und weisen fremde zusammengesetzte Eingaben mit wrong_owner zurück.

Diese Prüfungen laufen nur in Debug-Builds (CHECKED_ADMISSION). Ein Release-Build vertraut jedem zusammengesetzten Wort, Atom-, Pid- und Port-Wort, das einem Builtin, Service oder einer Fabrik übergeben wird, wie ERTS seinen Eterms vertraut: Erzeugter Code produziert nur Wörter des eigenen Prozesses, von Literalbereichen und den Atomen der Runtime, sodass die Bereichs- und Besitzerabfragen bei jedem Argument nur Kosten verursachten. Andere Immediates werden weiterhin klassifiziert. Tests mit gefälschten oder fremden Wörtern erwarten eine Zurückweisung nur bei CHECKED_ADMISSION. map_find darf den Schlüssel eines anderen Prozesses ohne diese Zurückweisung schreibgeschützt vergleichen. verify() bleibt für Tests die vollständige Prüfung, dass jeder Platz einen Objektanfang benennt.

Wurzeln und Safepoints

ProcessContext::visit_roots zählt jedes Wurzelwort für die Speicherbereinigung auf. An einem Safepoint hält nichts anderes Heap-Wörter des Prozesses:

BesitzerWurzelwörterAnmerkungen
Termplätze der FramesDie ersten roots Plätze jedes Frames auf dem StackUnterste Frames haben keine; Fortsetzungs- und Handler-Indizes sind Ganzzahlen
Rohe Frame-PlätzeKeineAusgelagerte native Werte; erzeugter Code hält dort an einem Safepoint kein Heap-Wort (Neuladeregel)
Registerx[0..live) (ProcessStack::keep_registers)Die Argumente eines suspendierten Eintritts; jedes Push und Pop leert live
FehlerkanalFehler-Nutzdaten (BEAM fvalue), Argumentliste von erlang:error/2,3, Stacktrace-TermGeneratedCallState-Terme werden durch Term::rebind umgeschrieben, das nur das neue Wort speichert; erfasste Trace-Frames sind Deskriptorzeiger in den Code
Trap-ZustandDie Termwörter des TrapState eines trappenden Builtins (Portionen)Freigegeben, wenn das Builtin endet oder scheitert
Prozess-DictionaryJeder Schlüssel und WertBis zum Löschen oder Ende des Prozesses
MailboxJede Nachricht im Signal-Eingang und in der Nachrichtenwarteschlange, einschließlich 'EXIT'- und 'DOWN'-NachrichtenBis ein receive sie entnimmt; an Ort und Stelle umgeschrieben, sodass der receive-Cursor (eine Listenposition) und die Timeout-Frist gültig bleiben
Explizite WurzelnDer Bereich, den ein Host an collect(roots) übergibtNach dem Aufruf zurückgelesen
Off-Heap-ListeKeineVerknüpfungen werden durchlaufen und neu eingehängt, nicht verfolgt

Keine Heap-Zelle hält einen Pin. Atome sind Immediates, und die Atomtabelle wird nie bereinigt. Fun-Zellen benennen Code über ihre nicht verfolgte FunDefinition, die so lange lebt wie die Runtime; geladene Module werden nie entladen, daher brauchen weder Funs noch Trace-Deskriptoren einen Pin. Kleine Immediates sind keine Wurzeln.

Wie im C-Code von ERTS ist ein Host-Term genau ein trivial kopierbares Wort, gültig bis zur nächsten Speicherbereinigung seines Heaps oder zum Prozessende; er hat weder Pin noch Heap-Zeiger, Lebensdauer-Token oder Bereinigungsepoche (Besitz). Ein Zugriff nach einer dieser Grenzen liest verschobenen oder freigegebenen Speicher und ist undefiniert, ohne stale_term-Prüfung. Registrierte Wurzeln werden umgeschrieben; ein nicht registrierter Host-Term muss aus seinem umgeschriebenen Wurzelwort neu aufgebaut werden. Ein Term gehört seinem Prozess; andere Prozesse dürfen ihn nur lesen, solange die Quelle stabil bleibt.

Der Heap bewegt sich nur an einem Safepoint und nie, während eine Reservierung offen ist:

Jede andere Anforderung liefert unsafe_point und ändert nichts, nicht einmal den Fehlerkanal eines laufenden erzeugten Aufrufs. Eine Allokation bewegt den Heap nie: Eine Anforderung, die nicht passt, erzeugt ein Fragment.

Nachrichtenpuffer

Ein MessageBuffer (message_buffer.hpp) hält einen Term außerhalb jedes Prozesses: copy_of(value, sender) kopiert den Graphen aus dem Prozess-Heap des Senders in einen eigenen HeapStorage. Der ProcessContext des Senders liefert Heap-Optionen, Literalbereiche, Atome, Identitäten und das Speicherkonto der Runtime; der Puffer behält sein eigenes Lebensdauer-Token und teilt Off-Heap-Binaries. Er verwendet dieselbe GraphCopy wie ein Senden; copy_to kopiert ihn in den Heap eines Empfängers. Immediates und Literalwurzeln brauchen keinen Speicher. Fabriken, Reservierungen und Nachrichtenpuffer behalten ihre Lebensdauerprüfungen (expired_context); ein aus einem Puffer gelesener Term pinnt nichts und darf seinen zugrunde liegenden Speicher nicht überleben. Der Puffer wird nie bereinigt und mit seinem letzten Besitzer freigegeben; sein Block umfasst genau die Wörter des Terms, unabhängig vom Mindest-Heap des Senders. Sendungen an andere Prozesse (Eingangspuffer), Timer-Nachrichten und Monitor-Tags verwenden ihn, sodass sie nie neu kodiert werden; persistente Terme leben in ihrem eigenen Adressbereich und werden in Puffer kopiert, nicht mit ihnen geteilt.

Ein Nachrichtendurchlauf. Eine Sendung an einen anderen Prozess durchläuft den Graphen ihrer Nachricht einmal, vor jeder Sperre (OutgoingMessage, mit der GraphCopy aus graph_copy.hpp); dieser Durchlauf bestimmt die Größe jeder späteren Kopie und kopiert wie jede Kopie außerhalb eines Prozesses persistente Terme.

Nachrichtenübergabe. Ein Senden, dessen Nachricht mindestens MessageBuffer::ADOPTED_WORDS (512) Wörter umfasst, geht immer über einen Puffer, der anhand dieses Durchlaufs vor jeder Sperre kopiert wird, sodass der Sender beim Kopieren keine Sperre hält. Der Empfänger übernimmt den Puffer dann, statt ihn zu kopieren (MessageBuffer::take, HeapStorage::adopt), wenn der Puffer keinen anderen Besitzer hat und der Empfänger einen Heap-Block und kein Budget hat: Der Block kommt an denselben Adressen zu seinen Fragmenten, die Off-Heap-Binary- und Map-Listen des Puffers kommen samt ihrer Anrechnungen zu seinen eigenen, und der Puffer bleibt leer zurück. Niemand sonst kann den Block vorher oder nachher erreichen, daher wird kein Wort zweimal kopiert. Übernommene Fragmente fordern erst dann eine Bereinigung an, wenn ein receive ihre Nachricht entnommen hat und ihre Wörter die des jungen Blocks erreichen, so wie ERTS ein Nachrichtenfragment erst nach dem Empfang zählt (HeapStorage::received); die nächste Bereinigung behandelt sie als junge Daten und kopiert eine noch eingereihte Nachricht wie jede Wurzel. Kleinere Nachrichten werden einmal kopiert, günstiger als je ein Fragment: unter dem Executor-Mutex direkt in den Heap eines nicht laufenden Empfängers oder unmittelbar hinter einen Eingangsknoten für einen laufenden. Der Empfänger übernimmt einen solchen Eingang, indem er dessen Wörter unverändert kopiert und die Zeiger zwischen ihnen um den Abstand der Kopie verschiebt; dabei liest er die Objekte in Adressreihenfolge, wie die Speicherbereinigung ihre Kopien durchsucht (verschobene Eingänge). Der Graph wird nicht erneut durchlaufen.

Bereinigung in erzeugtem Code

Erzeugter Code bereinigt nur an wenigen Safepoints, an denen jeder lebende Term bereits in einer Wurzel liegt. Alles andere, einschließlich jedes allozierenden Dienstes, ist ein kritischer Abschnitt, der den Heap nie bewegt.

Auslöser

Ein Safepoint bereinigt, wenn der Heap danach verlangt (wants_collection()); andernfalls kostet er eine Prüfung.

AuslöserBedingung am SafepointGegenstück in ERTS
Heap vollEs existiert ein Fragment: Seit der letzten Bereinigung passte eine Allokation nicht in den Heap-BlockHeap-Top erreicht das Heap-Ende
Druck durch Off-Heap-Binaries (eine kleine Bereinigung, außer die letzte hat weniger als die Hälfte freigegeben, Binary-Druck)Off-Heap-Wörter erreichen das Limit des virtuellen Binary-Heaps: anfangs 46.422 Wörter, nach jeder Bereinigung das Doppelte der überlebenden Off-Heap-Wörter oder die Überlebenden plus die lebenden Heap-Wörter, je nachdem, was größer ist (nie weniger als 46.422), aber höchstens die Überlebenden plus die Hälfte des nach dem Heap-Block frei verbleibenden Budgetsbin_vheap_sz / virtueller Binary-Heap
erlang:garbage_collect/0,1,2Eine Anforderung des Builtins (Introspektion), ausgeführt am nächsten Safepoint des ZielsExpliziter vollständiger Durchlauf

Ein Safepoint führt eine kleine Bereinigung (minor collection) aus, außer ein vollständiger Durchlauf ist fällig: eine garbage_collect-Anforderung, Druck durch Off-Heap-Binaries, ein Heap-Budget oder ein alter Bereich ohne Platz für die reifen Wörter (Generationen). Das öffentliche ProcessHeap::collect ist immer ein vollständiger Durchlauf.

Der neue Block wird für die lebenden Wörter plus die benutzten Stack-Wörter dimensioniert (ERTS hält den Stack innerhalb des Heap-Blocks): Ein tiefer Stack bekommt einen größeren Heap, sodass eine lange Rekursion proportional zu ihrer Allokation bereinigt, statt alle paar hundert Wörter den ganzen Stack erneut zu durchsuchen.

Safepoints

PunktWoLebend außerhalb der Termplätze der Frames
FunktionseintrittIn CLAUSE_enter_v1 / CLAUSE_tail_v1 (und damit beim Host-Aufruf), bevor der Frame des Aufgerufenen abgelegt wird; ein Inline-Eintritt nimmt diesen Weg, wann immer der Heap eine Bereinigung willDie Argumente des Aufgerufenen x[0..arity), als Wurzeln gehalten (keep_registers)
SchleifenkopfCLAUSE_safepoint_v1(context) am Kopf jeder Generatorschleife einer Comprehension, nur aufgerufen, solange der Heap eine Bereinigung willNichts

Jede Erlang-Schleife ist entweder eine Rekursion, die pro Schritt einen Funktionseintritt passiert, oder eine Comprehension, die ihren Schleifenkopf passiert, sodass der Müll zwischen zwei Safepoints durch geradlinigen Code und einzelne Dienstergebnisse begrenzt ist.

Keine Safepoints (kritische Abschnitte, die weiter in Fragmente allozieren): jeder andere Runtime-Dienst, einschließlich Allokations-, Konstruktions- und Matching-Diensten; CLAUSE_return_v1; Ausnahmeweitergabe; und Nachrichtenzustellung. Dienste dürfen daher während ihres gesamten Laufs rohe Heap-Wörter in C++ halten, und ihre Eingabearrays und Ausgaben müssen nicht neu geladen werden.

Wartende und suspendierte Prozesse

Ein Prozess, der nicht läuft, wird nie bereinigt: Er wartet in einem receive, ist nach einem Yield oder Trap eingereiht oder noch nicht gestartet, und alles, was er hält, ist bereits eine Wurzel (seine Frames, die Register des Eintritts oder der Fortsetzung, an dem bzw. der er fortfährt, der Trap-Zustand und seine Nachrichten). An ihn gesendete Nachrichten werden in Fragmente seines Heaps kopiert. Jede Zustellung weckt einen wartenden Prozess, und seine Fortsetzung wiederholt den Eintritt seiner Fortsetzung (das Warte-Builtin, eine Trap-Fortsetzung oder die Funktion, an der er per Yield abgegeben hat), was ein Safepoint beim Funktionseintritt ist: Das Erste, was ein fortgesetzter Prozess tut, ist zu bereinigen, wenn sein Heap danach verlangt. Ein Prozess, der in einem selektiven receive wartet, das viele Nachrichten überspringt, bereinigt daher, während sie eintreffen, so wie ERTS einen Prozess bereinigt, wenn er das nächste Mal eingeplant wird. Das Golden executables_mailbox_collection deckt Horten, Warten mit Timeout, tiefe Rekursion unter Nachrichtenlast und einen Konsumenten ab, der 3.000 Nachrichten innerhalb von --max-heap 65536 bestätigt.

Allokation ist kein Safepoint (anders als test_heap von BEAM): Das würde erfordern, dass jede Diensteingabe und jeder über eine Allokation hinweg lebende SSA-Term in einer Wurzel liegt, ein Neuladen nach jedem allozierenden Dienst und ein Wiederholungsprotokoll in jedem Dienst, während die beiden obigen Safepoints den Müll bereits begrenzen.

Neuladeregel

Kein SSA-Wert (ein Wert in einem nativen Register) hält ein Heap-Wort über einen Safepoint hinweg, und kein nativer Zeiger überquert überhaupt einen (bereits ein Fehler in lower_frames).

Fehlerverhalten

Implementierung

Prototyp

tests/prototypes/safepoint enthält eine Schleife im Stil einer Comprehension in der Form nach lower_frames (loop.ll): Ein vor der Schleife berechneter Term Y wird in einen Termplatz gespeichert und nach dem Safepoint am Schleifenkopf neu geladen. python tests/prototypes/safepoint/run.py kompiliert ihn für x86_64-pc-windows-msvc, aarch64-unknown-linux-gnu, i686-pc-windows-msvc und armv7-unknown-linux-gnueabihf bei O0 und O2 und prüft, dass auf den Safepoint-Aufruf ein Ladezugriff auf das Frame-Wort von Y folgt, sodass der Optimierer nie das Register wiederverwendet, das Y enthielt.

Speicherbereinigung

Eine Cheney-Kopie (memory/heap_collect), ein vollständiger Durchlauf oder eine kleine Bereinigung (Generationen): Zuerst wird der neue Block alloziert (ein Fehlschlag ist out_of_memory und lässt den Heap unberührt), dann wird das Objekt hinter jedem Wurzelwort kopiert und der neue Block von links nach rechts durchsucht, wobei die Kinder jeder Kopie kopiert werden. Ein Platz wird zuerst gegen den Quellbereich des vorherigen Platzes geprüft (ein Bereichstest), und Immediates werden inline unterschieden. Cons-Zellen werden ohne Parsen durchsucht, und die Plätze einer geboxten Kopie werden allein anhand der Art in ihrem Header gefunden (eine Anzahl nicht verfolgter Wörter pro Art, keine Größenprüfungen: Die Kopien stammen von verifizierten Objekten); Objekte mit bis zu vier Wörtern werden Wort für Wort kopiert. Der Header eines verschobenen geboxten Objekts wird durch einen geboxten Zeiger auf seine Kopie ersetzt; eine verschobene Cons-Zelle bekommt einen Null-Kopf und einen Rest, der auf ihre Kopie zeigt. Die Weiterleitung erhält geteilte Strukturen. Wurzeln werden an Ort und Stelle umgeschrieben, die Off-Heap-Liste wird durchlaufen (Kopien in Listenreihenfolge neu eingehängt, tote Zellen zerstört), und der alte Block und die Fragmente werden freigegeben. Ein Heap, der nie alloziert wurde, wird nicht bereinigt. CollectionStats meldet die Wörter davor, die lebenden Wörter, den neuen Heap-Block, die zusammengeführten Fragmente, die Kapazität der Stack-Plätze und die Off-Heap-Wörter.

Generationen

Generationelle Bereinigung, wie ERTS sie durchführt, ohne deren Schreibbarriere:

Kopieren zwischen Heaps

ProcessHeap::add(value), gleichbedeutend value.copy_to(heap), liefert einen Term des Ziel-Heaps (BEAM size_object und copy_struct). Quellzellen werden über vertrauenswürdige Zeiger gelesen, und eine Cons-Zelle muss nicht geparst werden:

Messungen

runtime_heap_measurements (CTest im Vollmodus; Zahlen werden ausgegeben, nicht geprüft) baut über TermFactory eine Liste von 100.000 Elementen aus {Index, Float}-Tupeln, durchläuft sie über geprüfte Zugriffsfunktionen, bereinigt mit der Liste als einziger Wurzel und durchläuft die Kopie und erzeugt 1.000 Kontexte, die je ein kleines Tupel halten. Er meldet Aufbau- und Durchlaufzeiten, belegte Heap-Wörter und Kapazität in Wörtern, Nebenbytes (Host-Allokationen über den Heap-Speicher hinaus) sowie Bytes und Heap-Wörter pro Kontext.