- Le langage Go présente très peu de comportements indéfinis et dispose d’une sémantique de GC (garbage collection) simple
- En Go, la gestion manuelle de la mémoire est possible, et peut être mise en œuvre en coopération avec le GC
- Une arena est une structure de données permettant d’allouer efficacement de la mémoire ayant la même durée de vie, et l’article explique comment l’implémenter en Go
- Il explique comment le GC gère la mémoire via l’algorithme Mark and Sweep
- L’utilisation d’une arena peut améliorer les performances d’allocation mémoire, grâce à diverses optimisations
- Tentative d’amélioration des performances et de minimisation de la charge du GC via la suppression des write barriers, la réutilisation mémoire, le chunk pooling, etc.
- Présentation de motifs sûrs et rapides pour le traitement réel de gros volumes mémoire, via des fonctionnalités comme l’implémentation de
realloc, la réutilisation d’arena et sa réinitialisation (Reset)
Vue d’ensemble de l’allocation manuelle de mémoire basée sur une arena en Go
- Go est un langage sûr grâce à un comportement de GC clair et à l’absence quasi totale d’Undefined Behavior
- Le package
unsafe permet un contrôle direct de la mémoire adapté à l’implémentation interne du GC
- Cet article explique comment construire en Go un allocateur mémoire basé sur une structure d’arena capable de coopérer avec le GC
Définition et utilité d’une arena
- Une arena est une structure conçue pour allouer efficacement des objets ayant la même durée de vie
- Là où un
append classique étend un tableau de façon exponentielle, une arena ajoute de nouveaux blocs et fournit des pointeurs
- L’interface standard est la suivante :
Alloc(size, align uintptr) unsafe.Pointer
Fonctionnement des pointeurs et du GC
- Le GC fonctionne en marquant (mark) puis en récupérant (sweep) la mémoire
- Pour un GC précis, il utilise des métadonnées appelées pointer bits qui indiquent l’emplacement des pointeurs
- Si les pointeurs sont mal gérés dans une arena, le GC peut ne pas les suivre, ce qui peut provoquer des erreurs de use-after-free
Méthode de conception d’une arena
- La structure d’arena possède les champs suivants :
- Toutes les allocations sont alignées sur 8 octets, et si l’espace manque, un nouveau chunk est créé via
nextPow2
- Les chunks sont alloués non pas comme
[]uintptr, mais avec un type struct { A [N]uintptr; P *Arena }, afin que le GC puisse suivre l’arena
Comment garantir la sûreté des pointeurs dans l’arena
- Lorsqu’on n’utilise que des pointeurs alloués à l’intérieur de l’arena, le GC conserve l’intégralité de l’arena
- En faisant référencer l’arena par les pointeurs, on garantit la survie de toute l’arena vis-à-vis du GC
- La méthode d’allocation de l’arena effectue notamment ceci :
- dans
allocChunk(), elle stocke le pointeur de l’arena à la fin du chunk
Résultats des benchmarks de performance
- Par rapport à
new de base, l’allocation par arena montre en moyenne des performances 2 à 4 fois supérieures, voire davantage
- Même dans des situations de forte charge GC, l’approche par arena montre des performances supérieures pouvant dépasser un facteur 2
- Des optimisations comme la suppression des write barriers ou l’usage de
uintptr apportent jusqu’à 20 % de gain sur les petites allocations
Réutilisation des chunks et stratégie de suppression du heap
- Il est possible de réutiliser les chunks avec
sync.Pool
runtime.SetFinalizer() permet de renvoyer les chunks dans le pool quand l’arena disparaît
- Les performances s’améliorent nettement sur les petites allocations, mais peuvent devenir inférieures à
new sur les grosses allocations
Réinitialisation et réutilisation de l’arena
- La méthode
Reset() permet de ramener l’arena à son état initial
- C’est potentiellement risqué, mais cela permet de réutiliser la même structure sans réallouer la mémoire
- La réutilisation des chunks lors de cette remise à zéro améliore fortement les performances
Implémentation de la fonctionnalité Realloc
- L’arena implémente une fonctionnalité de
realloc permettant une extension dynamique de l’allocation la plus récente
- Quand ce n’est pas possible, une nouvelle zone mémoire est allouée puis les données sont copiées
Conclusion et code complet fourni
- En comprenant en profondeur le mécanisme de GC de Go et en s’appuyant sur son implémentation interne, l’auteur aboutit à un gestionnaire mémoire basé sur une arena
- La structure combine sûreté et performances, et peut être très utile pour traiter de grandes structures de données lorsqu’elle est utilisée à bon escient
- Le code complet inclut la structure Arena ainsi que
New, Alloc, Reset, allocChunk, finalize, etc.
1 commentaires
Commentaire Hacker News
Cet article est une lecture intéressante
Reference[T]offrant la même fonctionnalitéEn faisant récemment du tuning de performance en Go, j’ai fini par utiliser un design d’arène très similaire pour maximiser les performances
unsafeQuelques améliorations simples
capde façon plus agressive avant d’appeler l’appendintégréunsafe.Stringest utile pour passer une chaîne depuis une slice d’octets sans allocationHors sujet, mais j’aime bien la mini-carte sur le côté
Résumé pour ceux qui hésitent à lire un long article
unsafepour accélérer le travail d’allocationunsafe.Pointer, le GC ne peut pas correctement voir ce qui est pointé depuis l’arène et peut le libérer par erreurreflect.StructOfpour créer un nouveau type incluant des champs pointeurs supplémentaires vers ces blocsConnexe : discussion sur l’ajout de « zones mémoire » à la bibliothèque standard
C’est intéressant
Go donne la priorité à l’absence de rupture de l’écosystème
Petite note méta