- Die Sprache Go hat fast kein undefiniertes Verhalten und verfügt über eine einfache GC-(Garbage-Collection-)Semantik
- In Go ist manuelle Speicherverwaltung möglich, und sie kann in Zusammenarbeit mit dem GC erfolgen
- Eine Arena ist eine Datenstruktur, die Speicher mit gleicher Lebensdauer effizient allokiert; erklärt wird, wie sich das in Go umsetzen lässt
- Es wird erklärt, wie der GC Speicher mit dem Mark-and-Sweep-Algorithmus verwaltet
- Mit Arenas lässt sich die Speicherallokations-Performance verbessern, was durch verschiedene Optimierungen möglich wird
- Durch Entfernen der Write Barrier, Wiederverwendung von Speicher, Chunk Pooling usw. wird versucht, die Performance zu steigern und die GC-Last zu minimieren
- Es werden sichere und schnelle Muster für die Verarbeitung großer Speichermengen in der Praxis vorgestellt, etwa durch Realloc-Implementierung, Arena-Wiederverwendung und Initialisierung (Reset)
Überblick über Arena-basierte manuelle Speicherallokation in Go
- Go ist dank klarem GC-Verhalten und nahezu fehlendem Undefined Behavior eine sichere Sprache
- Mit dem Paket
unsafe ist eine direkte Kontrolle über Speicher passend zur internen GC-Implementierung möglich
- Dieser Artikel erklärt, wie man in Go einen Arena-strukturbasierten Speicherallokator baut, der mit dem GC zusammenarbeiten kann
Definition und Zweck von Arenas
- Eine Arena ist eine Struktur, um Objekte mit derselben Lebensdauer effizient zu allokieren
- Während ein gewöhnliches
append ein Array exponentiell erweitert, fügt eine Arena neue Blöcke hinzu und liefert Pointer zurück
- Die Standardschnittstelle sieht wie folgt aus:
Alloc(size, align uintptr) unsafe.Pointer
Pointer und Funktionsweise des GC
- Der GC arbeitet, indem er Speicher verfolgt (mark) und einsammelt (sweep)
- Für einen präzisen GC werden Metadaten namens pointer bits verwendet, die Informationen über Pointer-Positionen liefern
- Wenn Pointer in einer Arena falsch behandelt werden, kann der GC sie nicht verfolgen, was zu Use-After-Free-Fehlern führen kann
Entwurf einer Arena
- Die Arena-Struktur hat die folgenden Felder:
- Alle Allokationen werden mit 8-Byte-Ausrichtung verarbeitet; reicht der Platz nicht aus, wird mit
nextPow2 ein neuer Chunk erzeugt
- Chunks werden nicht als
[]uintptr, sondern als struct { A [N]uintptr; P *Arena } allokiert, damit der GC die Arena verfolgen kann
Wie die Pointer-Sicherheit der Arena sichergestellt wird
- Wenn nur innerhalb der Arena allokierte Pointer verwendet werden, hält der GC die gesamte Arena am Leben
- Indem Pointer so gesetzt werden, dass sie auf die Arena verweisen, wird das Überleben der gesamten Arena im GC garantiert
- Die Allokationsmethode der Arena führt Folgendes aus:
- In
allocChunk() wird der Arena-Pointer am Ende des Chunks gespeichert
Ergebnisse der Performance-Benchmarks
- Gegenüber einfachem
new zeigt Arena-Allokation im Schnitt eine 2- bis 4-fache oder höhere Performance-Steigerung
- Auch in Situationen mit hoher GC-Last zeigt der Arena-Ansatz eine um mehr als das 2-Fache bessere Performance
- Optimierungen wie das Entfernen der Write Barrier oder die Nutzung von
uintptr bringen bei kleinen Allokationen bis zu 20 % Performance-Gewinn
Strategien zur Chunk-Wiederverwendung und Heap-Vermeidung
- Mit
sync.Pool lassen sich Chunks wiederverwenden
- Über
runtime.SetFinalizer() können Chunks in den Pool zurückgegeben werden, wenn die Arena verschwindet
- Die Performance verbessert sich bei kleinen Allokationen stark, kann bei großen Allokationen aber langsamer als
new sein
Initialisierung und Wiederverwendung von Arenas
- Mit der Methode
Reset() lässt sich eine Arena in ihren Anfangszustand zurückversetzen
- Das ist riskant, ermöglicht aber die Wiederverwendung derselben Struktur ohne erneute Speicherallokation
- Auch bei der Wiederverwendung werden Chunks wiederverwendet, was die Performance deutlich steigert
Implementierung einer Realloc-Funktion
- In der Arena wird eine
realloc-Funktion implementiert, die die dynamische Erweiterung der zuletzt erfolgten Allokation ermöglicht
- Ist das nicht möglich, wird neuer Speicher allokiert und anschließend kopiert
Fazit und vollständiger Code
- Durch tiefes Verständnis des GC-Mechanismus von Go und auf Basis der internen Implementierung entsteht ein Arena-basierter Speicherverwalter
- Die Struktur vereint Sicherheit und Performance und ist bei angemessenem Einsatz sehr nützlich für die Verarbeitung großer Datenstrukturen
- Der vollständige Implementierungscode umfasst die Arena-Struktur sowie
New, Alloc, Reset, allocChunk, finalize usw.
1 Kommentare
Hacker-News-Kommentar
Dieser Artikel ist eine unterhaltsame Lektüre
Reference[T]-Typ mit derselben FunktionalitätBeim jüngsten Performance-Tuning in Go habe ich ein sehr ähnliches Arena-Design verwendet, um die Leistung zu maximieren
unsafe-Zeigern habe ich Byte-Slices als Buffer und Chunks verwendetEin paar einfache Verbesserungen
appendschreiben, das diecapaggressiver erhöht, bevor das eingebauteappendaufgerufen wirdunsafe.Stringist nützlich, um Strings aus Byte-Slices ohne Allokation weiterzugebenDas hat nichts mit dem Thema zu tun, aber mir gefällt die Minimap an der Seite
Zusammenfassung für Leute, die lange Artikel nur ungern lesen
unsafeeinen Arena-Allokator gebaut, um Allokatorarbeit zu beschleunigenunsafe.Pointerallokiert, kann der GC Verweise aus der Arena möglicherweise nicht korrekt sehen und etwas versehentlich freigebenchunks) beibehalten, das auf alle großen Speicherblöcke zeigt, die die Arena vom System erhalten hat,reflect.StructOfverwendet, um einen neuen Typ zu erzeugen, der zusätzliche Zeigerfelder auf diese Blöcke enthältVerwandt: Diskussion über das Hinzufügen von "Speicherbereichen" zur Standardbibliothek
Interessanter Stoff
Go priorisiert es, das Ökosystem nicht kaputtzumachen
Eine kurze Meta-Anmerkung