Go Runtime nimmt Ihnen die Ruhe einer Tasse Grüntee, GreenTea GC
Warum ein weiterer neuer GC?
Warum der bestehende GC?
Der aktuelle GC von Go lässt sich wie folgt beschreiben:
- concurrent tri-color mark and sweep
- Es werden drei Farben (weiß, grau, schwarz) verwendet, um den Status von Objekten zu verfolgen.
- Weiß: Noch nicht besuchte Objekte
- Grau: Besucht, aber nicht alle Kindobjekte besucht
- Schwarz: Alle Kindobjekte besucht
- Nach Abschluss des Durchlaufs werden alle weißen Objekte gesammelt.
- Ein Write Barrier wird verwendet, um neu erstellten Speicher während der GC zu schützen.
- Die Zeit, die für das Ein- und Ausschalten des Write Barrier benötigt wird, macht einen Großteil des sogenannten Stop-The-World (im Folgenden STW) aus.
- Dieser Prozess wird gleichzeitig ausgeführt.
- Es werden drei Farben (weiß, grau, schwarz) verwendet, um den Status von Objekten zu verfolgen.
- No Compaction
- Der GC von Go führt keine Komprimierung durch.
- Es kann zu Speicherfragmentierung kommen.
- Objekte unter 32 KB minimieren jedoch die Fragmentierung, indem sie den per-P-Cache des Go-Speicherallokators (Allocator) nutzen.
- Non-Generational
- Der GC von Go verwaltet Objekte nicht generationenweise.
- Alle Objekte gehören zur selben Generation.
- Escape Analysis
- Go bestimmt mittels Escape Analysis, ob ein Objekt im Heap oder im Stack zugewiesen wird.
- Grob gesagt, wenn Dangling Pointers oder Interfaces verwendet werden, kann davon ausgegangen werden, dass sie im Heap zugewiesen werden.
Das Wichtige
Der GC von Go durchsucht alle Objekte ausgehend vom Root und führt eine dreifarbige Markierung durch. Dieser Vorgang lässt sich in einem Satz als "gleichzeitiger Traversierungsalgorithmus für topologisch sortierte Graphen" beschreiben. Es ist jedoch sehr wahrscheinlich, dass sich die einzelnen Objekte in unterschiedlichen Speicherbereichen befinden. Um es auf den Punkt zu bringen:
- Nehmen wir an, es gibt Speicherbereiche, die etwa 32 MB voneinander entfernt sind.
- In diesen beiden Speicherbereichen sind jeweils 5 Objekte zugewiesen.
- Objekte A, B, C im Bereich 1
- Objekte D, E im Bereich 2
- Die Referenzreihenfolge der Objekte ist
A -> D -> B -> E -> C
. - Wenn der GC nun startet, werden A, dann D, dann B, dann E und schließlich C besucht.
- Da A und D in unterschiedlichen Speicherbereichen liegen, muss der Speicherbereich beim Besuch von A nach D gewechselt werden.
- Dieser Vorgang, der das Wechseln von Speicherbereichen und das Füllen neuer Cache-Zeilen beinhaltet, verursacht sogenannte Speicher-Sprung-Kosten. Ein erheblicher Teil der von der Anwendung genutzten CPU-Zeit kann für solche GC-Aufgaben aufgewendet werden.
- Diese Kosten fallen während des gesamten GC-Vorgangs an.
Was ist also das Problem?
Dieses Verhalten des GC ist in den folgenden Fällen Gift:
- Viele Cores und großer Speicher
- Der GC von Go wird grundsätzlich gleichzeitig ausgeführt.
- Wenn der Speicherbereich jedoch groß und die Objekte verteilt sind, durchsuchen mehrere Cores gleichzeitig verschiedene Speicherbereiche nach Objekten.
- Wenn der Bus zwischen CPU und Speicher in diesem Prozess nicht ausreichend groß ist, kann die Speicherbandbreite zum Engpass werden.
- Da auch eine physische Distanz besteht, wird jeder einzelne Scan eine vergleichsweise große Verzögerung verursachen.
- Viele kleine Objekte
- Wenn Sie tiefe oder viele Kinder enthaltende Bäume oder Graphen in Go betreiben, muss der GC alle diese Objekte durchsuchen.
- Wenn die Objekte in diesem Prozess auf verschiedene Speicherbereiche verteilt sind, kommt es aus dem ersten Grund zu Verzögerungen.
- Wenn es viele kleine Objekte gibt, muss der GC all diese Objekte durchsuchen, sodass während der Ausführung des GC ein erheblicher Teil der CPU-Cores für den GC-Prozess aufgewendet und genutzt wird.
Um diese Probleme zu beheben, hat das Golang-Team den GreenTea GC vorgestellt.
Sie haben keinen weiteren Grünen Tee mehr übrig
Was entzieht Ihnen den Grünen Tee?
Der Bereich, in dem die schnellste Lösung für den bestehenden GC angewendet werden kann, scheint die Speicherlokalität zu sein. Das bedeutet, Objekte sollen nahe beieinander liegen, um Speichersprünge zu minimieren. Die Muster der Programmierer können jedoch nicht erzwungen werden, und die Objektspeicherung ist je nach Workflow nicht vorhersehbar.
Deshalb hat das Golang-Team die Memory Span gewählt.
Was ist ein Memory Span?
Ein Memory Span ist ein relativ großer Speicherbereich, der zugewiesen wird, um kleine Objekte zuzuweisen. Und der neue GC, der den Namen GreenTea GC trägt, führt die Garbage Collection für diesen Memory Span durch. Das detaillierte Verhalten ist nahezu identisch mit dem bestehenden Tri-Color Mark and Sweep.
Funktionsweise des GreenTea GC
Zunächst weist der GreenTea GC einen Memory Span zu. Die Größe beträgt, wie bereits erwähnt, eine gewisse Größe von 8 KiB. Und darin können Objekte mit einer maximalen Größe von 512 Bytes zugewiesen werden. Dies ist eine Größe, die ungefähr der Knotengröße von Bäumen oder Graphen entspricht, wie sie als Beispiel genannt wurden, oder eine Größe, bei der es schwierig ist, dass die Größe einer allgemeinen Struktur, die in den Heap entweicht, größer ist. Objekte unter 512 Bytes werden jedes Mal, wenn sie in den Heap entweichen, in diesem Memory Span angesammelt, und wenn dieser Memory Span voll ist, wird ein neuer Memory Span zugewiesen.
Wenn nun eine GC auftritt, stapelt der GreenTea GC diese Memory Spans in einer Warteschlange und überprüft sie sequentiell. Dabei verwendet der GreenTea GC ein Scheduling, das dem bestehenden GMP-Modell sehr ähnlich ist. Auch Bereiche wie Work Stealing sind implementiert. Jedenfalls überprüft der Worker, der einen Memory Span aus der Warteschlange entnimmt, die internen Objekte des ihm zugewiesenen Memory Span. Dabei wird der Tri-Color Mark and Sweep identisch angewendet.
Welche Vorteile bietet dies?
Der Hauptunterschied zum bestehenden GC in diesem Prozess ist nur einer: Die Einheit, in der die GC auf einmal ausgeführt wird, hat sich von Objekt zu Memory Span geändert. Dadurch bietet die GC folgende Vorteile:
- Speicherlokalität: Da Objekte in einem Memory Span gruppiert sind, werden Speichersprünge beim Durchsuchen von Objekten minimiert. Das bedeutet, der CPU-Cache kann maximal genutzt werden.
- Verbesserung der GC-Leistung: Durch die Durchführung der GC auf Memory-Span-Basis arbeitet die GC in einer Multi-Core-Umgebung effizienter.
- Speicherzuweisungsoptimierung: Da Memory Spans mit fester Größe zugewiesen werden, reduziert sich der Overhead bei der Zuweisung kleiner Objekte, und die Wahrscheinlichkeit der Fragmentierung nimmt ab. Dies erhöht die Effizienz der Speicherfreigabe.
Kurz gesagt, wir können jetzt häufiger und mit leichterem Herzen Objekte unter 512 Bytes zuweisen.
Allerdings gibt es auch hier gewisse vorteilhafte Bereiche.
- Baum-/Graphenstruktur: Wenn der Fan-Out hoch ist und sich nicht oft ändert
- B+ Baum, Trie, AST (Abstract Syntax Tree)
- Cache-freundliche Datenstrukturen
- Batch-Verarbeitung: Workflow für die Massenzuweisung kleiner Daten
- Viele kleine Objekte, die durch JSON-Parsing erzeugt werden
- DAO-Objekte von Datenbank-Resultsets
In diesen Fällen kann der GreenTea GC eine bessere Leistung als der bestehende GC erzielen. Dies gilt jedoch nicht für alle Fälle, und insbesondere wenn Objekte auf verschiedene Speicherbereiche verteilt sind, ist eine dramatische Leistungssteigerung immer noch schwer zu erwarten.
Fazit
Das Golang-Team scheint beabsichtigt zu haben, den GC langfristig weiter zu verbessern. Dieser GreenTea GC kann als eine kleinere Änderung in einem kleinen Bereich angesehen werden und ersetzt den bestehenden GC nicht. Der GreenTea GC scheint jedoch ein interessantes Beispiel zu sein, das einen Einblick in die Probleme gibt, mit denen das Golang-Team konfrontiert ist oder die es erwartet. Obwohl es sich um ein überraschend komplexes Problem handelt, war der Versuch, es durch die Hinzufügung eines relativ einfachen Konzepts zu lösen, ebenfalls beeindruckend. Persönlich halte ich es für ein interessantes Beispiel.
Der GreenTea GC ist eine experimentelle Funktion, die ab Go 1.25 eingeführt wird. Er kann durch Aktivieren der Umgebungsvariablen GOEXPERIMENT=greenteagc
verwendet werden. Da diese Funktion noch experimentell ist, sind vor dem Einsatz in einer Produktionsumgebung ausreichende Tests erforderlich.