GreenTea GC : La sérénité d'une tasse de thé vert, subtilisée par le runtime de Go
Pourquoi un nouveau GC ?
Pourquoi le GC existant ?
Le GC actuel de Go peut être décrit de la manière suivante :
- Marquage-balayage concurrent tri-couleur
- Il utilise trois couleurs (blanc, gris, noir) pour suivre l'état des objets.
- Blanc : objet non encore visité.
- Gris : objet visité mais dont tous les objets enfants n'ont pas encore été visités.
- Noir : objet dont tous les objets enfants ont été visités.
- À la fin de la traversée, tous les objets blancs sont collectés.
- Il utilise un Write Barrier pour protéger la mémoire nouvellement créée pendant le GC.
- Le temps nécessaire pour activer/désactiver le Write Barrier représente une part significative de ce que l'on appelle communément le Stop-The-World (ci-après STW).
- Ce processus est exécuté de manière concurrente.
- Il utilise trois couleurs (blanc, gris, noir) pour suivre l'état des objets.
- Pas de compaction
- Le GC de Go n'effectue pas de compaction.
- Cela peut entraîner une fragmentation de la mémoire.
- Cependant, pour les objets de moins de 32 Ko, la fragmentation est minimisée en utilisant le cache per-P de l'allocateur de mémoire du langage Go.
- Non-générationnel
- Le GC de Go ne gère pas les objets par génération.
- Tous les objets appartiennent à la même génération.
- Escape Analysis
- Go utilise l'Escape Analysis pour déterminer si un objet est alloué sur le tas (heap) ou sur la pile (stack).
- En gros, si vous utilisez des pointeurs suspendus ou des interfaces, on peut considérer qu'ils sont alloués sur le tas.
L'essentiel
Le GC de Go explore tous les objets à partir de la racine et effectue un marquage tricolore. Ce processus peut être résumé en une phrase : algorithme de traversée concurrente de graphe avec tri topologique
. Cependant, il est fort probable que chaque objet existe dans une zone de mémoire différente. Pour être plus précis,
- Supposons qu'il existe deux zones de mémoire éloignées d'environ 32 Mo.
- Cinq objets sont alloués dans chacune de ces deux zones de mémoire.
- Les objets A, B, C se trouvent dans la zone 1.
- Les objets D, E se trouvent dans la zone 2.
- L'ordre de référence des objets est
A -> D -> B -> E -> C
. - Lorsque le GC démarre, il visite A, puis D, puis B, puis E, puis C.
- À ce moment-là, comme A et D existent dans des zones de mémoire différentes, il est nécessaire de se déplacer entre les zones de mémoire lors de la visite de A et de D.
- Ce processus entraîne ce que l'on appelle un coût de "saut de mémoire", impliquant le déplacement entre les zones de mémoire et le remplissage de nouvelles lignes de cache. Une part significative du temps CPU utilisé par l'application peut être allouée à ces opérations de GC.
- Ce coût est constant pendant l'exécution du GC.
Quel est le problème alors ?
Le fonctionnement de ce GC est particulièrement problématique dans les cas suivants :
- Nombre élevé de cœurs et grande quantité de mémoire
- Le GC de Go s'exécute fondamentalement de manière concurrente.
- Cependant, si la zone de mémoire est étendue et que les objets sont dispersés, plusieurs cœurs exploreront simultanément les objets dans divers espaces de mémoire.
- Dans ce processus, si le bus entre le CPU et la mémoire n'est pas suffisamment large, la bande passante mémoire peut devenir un goulot d'étranglement.
- De plus, en raison de la distance physique, chaque exploration entraînera un délai relativement important.
- Nombre élevé d'objets de petite taille
- Si vous utilisez des arbres ou des graphes avec une profondeur importante ou de nombreux enfants en Go, le GC doit explorer tous ces objets.
- Si les objets sont dispersés dans différentes zones de mémoire au cours de ce processus, un délai se produit en raison de la première raison.
- De plus, s'il y a de nombreux objets de petite taille, le GC doit explorer tous ces objets, ce qui signifie que les cœurs du CPU alloueront une capacité considérable et travailleront pour le GC pendant l'exécution du GC.
Pour résoudre ces problèmes, l'équipe Golang a annoncé le GreenTea GC.
Vous ne pouvez plus vous permettre de boire plus de thé vert
Qu'est-ce qui prive le thé vert ?
La localité de la mémoire semble avoir été identifiée comme le domaine où la solution la plus rapide aux problèmes du GC existant pouvait être appliquée. Autrement dit, minimiser les sauts de mémoire en positionnant les objets à proximité les uns des autres. Cependant, il n'est pas possible de contraindre les schémas de programmation des développeurs, et l'allocation d'objets est imprévisible en fonction du flux de travail.
C'est pourquoi la méthode choisie par l'équipe Golang est le Memory Span.
Qu'est-ce qu'un Memory Span ?
Un Memory Span est une zone de mémoire relativement grande allouée pour y stocker de petits objets. Le nouveau GC, nommé GreenTea GC, effectue la collecte des déchets sur ces Memory Spans. Son fonctionnement détaillé est quasiment identique à celui du Tri-Color Mark and Sweep existant.
Fonctionnement du GreenTea GC
Tout d'abord, le GreenTea GC alloue un Memory Span. Comme mentionné précédemment, sa taille est de 8 Kio, ce qui est une taille raisonnable. Et à l'intérieur, il peut allouer des objets d'une taille maximale de 512 octets. C'est une taille qui rend difficile pour la taille des nœuds d'un arbre ou d'un graphe, ou la taille des structures s'échappant vers le tas (heap) en général, d'être plus grande que cela. Les objets de moins de 512 octets s'accumulent dans ce Memory Span chaque fois qu'ils s'échappent vers le tas, et lorsqu'il est plein, un nouveau Memory Span est alloué.
Lorsque le GC se déclenche, le GreenTea GC met ces Memory Spans en file d'attente et les examine séquentiellement. Au cours de ce processus, le GreenTea GC utilise une planification presque identique au modèle GMP existant. Des zones telles que le vol de travail sont également implémentées. De toute façon, le travailleur qui retire un Memory Span de la file d'attente examine les objets internes du Memory Span qui lui est alloué. Le Tri-Color Mark and Sweep est utilisé de la même manière au cours de ce processus.
Quels sont les avantages ?
La différence principale avec le GC existant est la suivante : l'unité d'exécution du GC est passée de l'objet au Memory Span. Cela confère au GC les avantages suivants :
- Localité de la mémoire : Les objets étant regroupés dans des Memory Spans, les sauts de mémoire sont minimisés lors de l'exploration des objets. Cela permet une utilisation maximale du cache CPU.
- Amélioration des performances du GC : En effectuant le GC par unité de Memory Span, le GC fonctionne plus efficacement dans un environnement multicœur.
- Optimisation de l'allocation mémoire : Les Memory Spans étant alloués avec une taille fixe, la surcharge d'allocation mémoire pour les petits objets est réduite, et la probabilité de fragmentation diminue. Cela améliore l'efficacité de la désallocation mémoire.
En bref, nous pouvons désormais allouer des objets de moins de 512 octets plus fréquemment et avec une plus grande légèreté.
Cependant, il existe des scénarios où cela est particulièrement avantageux :
- Structures arborescentes/graphiques : lorsque le fan-out est élevé et que les modifications sont rares.
- Arbre B+, Trie, AST (Abstract Syntax Tree)
- Structures de données optimisées pour le cache.
- Traitement par lots : flux de travail d'allocation massive de petites données.
- Nombreux petits objets générés par l'analyse JSON.
- Objets DAO des jeux de résultats de bases de données.
Dans ces cas, le GreenTea GC peut offrir de meilleures performances que le GC existant. Cependant, il ne s'applique pas à tous les cas, et en particulier lorsque les objets sont dispersés dans différentes zones de mémoire, il est toujours difficile d'attendre une amélioration spectaculaire des performances.
En conclusion
L'équipe Golang semble avoir l'intention de continuer à améliorer le GC à long terme. Ce GreenTea GC peut être considéré comme un changement mineur dans un petit domaine et ne remplace pas le GC existant. Cependant, le GreenTea GC semble être un cas intéressant qui permet d'entrevoir les problèmes auxquels l'équipe Golang est confrontée ou qu'elle anticipe. Bien qu'il s'agisse d'un problème étonnamment complexe, la tentative de le résoudre par l'ajout d'un concept relativement simple a également été impressionnante. Personnellement, je pense que c'est un cas intéressant.
Le GreenTea GC est une fonctionnalité expérimentale introduite à partir de Go 1.25. Il peut être activé en utilisant la variable d'environnement GOEXPERIMENT=greenteagc
. Cette fonctionnalité est encore expérimentale, et des tests suffisants sont nécessaires avant de l'utiliser en production.