GC Doctor Analysez vos logs de ramasse-miettes dans le navigateur Analyser votre log GC dans GC Doctor

English

Glossaire GC

Glossaire GC Go

Termes que l'on rencontre dans la sortie du runtime Go (GODEBUG=gctrace=1 et scavtrace=1). Chaque entrée dit ce que le terme signifie, comment il apparaît dans une trace et quel réglage agit dessus.

Documentation officielle

Barrière d'écriture (Go)

Petit morceau de code que le compilateur ajoute aux écritures de pointeurs, actif seulement pendant le marquage.

Go marque en concurrence, pendant que le programme tourne. Si le programme stockait un pointeur vers un objet non marqué dans un objet déjà parcouru, le collecteur le manquerait et libérerait un objet vivant. La barrière d'écriture, un hybride de barrières de suppression et d'insertion, grise les objets concernés pour que ce soit impossible.

En dehors du marquage la barrière est inactive : son coût ne s'applique que pendant un cycle. C'est une des raisons pour lesquelles l'utilisation du CPU et la latence d'allocation sont plus élevées pendant un cycle.

gctrace

Ligne que le runtime Go écrit sur la sortie d'erreur à chaque collecte quand GODEBUG=gctrace=1 est défini. GC Doctor lit ces lignes.

Une ligne commence par le numéro du cycle et le temps écoulé depuis le démarrage, puis la part du temps passée en GC. Elle donne les temps réels des trois phases (sweep termination stop-the-world, marquage concurrent, mark termination stop-the-world), les temps CPU (assistance, arrière-plan, inactif), la taille du tas au début, à la fin et vivant, l'objectif de tas, les tailles des piles et des globales, et le nombre de processeurs.

La référence de chaque champ est la documentation du paquet runtime. GODEBUG=scavtrace=1 ajoute les lignes du scavenger.

Documentation officielle : Package runtime: environment variables (GODEBUG gctrace, scavtrace)

GOGC

Le réglage principal du GC de Go : le pourcentage du tas vivant qui peut être alloué avant le démarrage du cycle suivant. La valeur par défaut est 100.

Avec GOGC=100, l'objectif de tas est le double du tas vivant (plus piles et globales, depuis Go 1.18) : un programme avec 100 Mio de tas vivant démarre un cycle vers 200 Mio. Doubler GOGC double la mémoire supplémentaire et divise à peu près par deux le coût CPU du GC, et inversement. On le règle par la variable d'environnement GOGC ou debug.SetGCPercent. GOGC=off désactive le collecteur, ce qui n'est raisonnable qu'avec une limite mémoire.

Dans gctrace, comparez les tailles de tas (au début, à la fin, vivant) avec l'objectif. Un programme au tas vivant petit et stable avec beaucoup de mémoire disponible peut augmenter GOGC pour dépenser moins de CPU en GC. Un programme en conteneur proche de sa limite mémoire doit plutôt utiliser GOMEMLIMIT.

Documentation officielle : A Guide to the Go Garbage Collector

GOMEMLIMIT

Limite souple de la mémoire totale utilisée par le runtime Go, disponible depuis Go 1.19. Le collecteur travaille davantage à mesure qu'on s'en approche.

La limite couvre tout le runtime : tas, piles et structures du runtime, pas la mémoire rendue au système. Quand le total s'en approche, le collecteur tourne plus souvent que GOGC seul ne l'exigerait, échangeant du CPU contre de la mémoire. La limite est souple : si les données vivantes la dépassent vraiment, le programme continue de tourner, et le limiteur de CPU du GC empêche le collecteur de monopoliser le CPU.

Un réglage typique en conteneur est une limite de 5 à 10 pour cent sous la mémoire du conteneur, avec GOGC inchangé ou désactivé. On la règle par la variable GOMEMLIMIT (par exemple 900MiB) ou debug.SetMemoryLimit. Quand la limite contraint, les cycles deviennent fréquents et la part de CPU passée en GC augmente.

Documentation officielle : A Guide to the Go Garbage Collector

Goroutine

Thread léger géré par le runtime Go, assez peu coûteux pour en lancer des milliers ou des millions.

Les goroutines démarrent avec une pile de quelques kilo-octets qui grandit et rétrécit, et l'ordonnanceur les multiplexe sur un petit nombre de threads du système. Pour le ramasse-miettes elles comptent de trois façons. Leurs piles sont des racines à parcourir. Leurs piles comptent dans l'objectif de tas (depuis Go 1.18 l'objectif inclut piles et globales). Et une goroutine qui alloue pendant un cycle peut être réquisitionnée pour aider au marquage (mark assist).

Les goroutines qui fuient, bloquées à jamais sur un canal, retiennent tout ce que leurs piles référencent : une cause fréquente de mémoire qui croît sans cesse.

Limiteur de CPU du GC

Garde-fou ajouté en Go 1.19 qui plafonne le temps CPU que le collecteur peut prendre, autour de 50 pour cent, quand la limite mémoire est atteinte.

Avec une limite mémoire proche du tas vivant, le collecteur pourrait tourner en continu et affamer le programme, une spirale de la mort. Le limiteur suit la fraction de CPU utilisée par le GC et les assistances sur une fenêtre glissante et, quand elle dépasse 50 pour cent, laisse le programme allouer au-delà de la limite.

La limite mémoire est donc souple : le programme peut dépasser GOMEMLIMIT temporairement plutôt que de cesser de progresser. Si gctrace montre des cycles enchaînés avec un tas près de la limite, le tas vivant est trop grand pour la limite : relevez-la ou réduisez l'usage mémoire.

Documentation officielle : A Guide to the Go Garbage Collector

Mark assist

Travail de marquage fait par une goroutine qui alloue, quand le programme alloue plus vite que les workers d'arrière-plan ne marquent.

Pendant un cycle, le runtime essaie de finir le marquage avant que le tas atteigne l'objectif. Des workers d'arrière-plan dédiés utilisent 25 pour cent des processeurs. Si une goroutine alloue plus vite qu'ils n'avancent, elle est mise à contribution et doit faire une quantité proportionnelle de marquage avant que son allocation puisse continuer : une assistance.

Les assistances se voient dans la ligne gctrace comme le premier nombre du triplet de temps CPU (assistance, arrière-plan, inactif) et comme latence supplémentaire pour les requêtes qui allouent beaucoup. Des assistances persistantes suggèrent que l'objectif de tas est trop proche du tas vivant (augmentez GOGC ou la limite mémoire) ou que le débit d'allocation est trop élevé.

Documentation officielle : A Guide to the Go Garbage Collector

Objectif de tas

La taille totale de tas à laquelle le runtime Go veut que la collecte en cours soit terminée.

À la fin de chaque cycle, le runtime calcule l'objectif à partir du tas vivant, des piles, des globales et de GOGC (objectif = vivant + (vivant + piles + globales) x GOGC / 100), ou de la limite mémoire si elle est plus basse. Le régulateur démarre le marquage assez tôt pour finir quand le tas atteint l'objectif : plus tôt quand le débit d'allocation est élevé ou le marquage lent.

Dans gctrace l'objectif apparaît dans le champ MB goal. Si le tas le dépasse régulièrement, le régulateur perd la course : attendez-vous à des mark assists et à peu de marge.

Documentation officielle : A Guide to the Go Garbage Collector

Scavenger

La tâche d'arrière-plan qui rend la mémoire de tas inutilisée au système d'exploitation.

Après un pic de tas, le runtime garde la mémoire libérée pour la réutiliser. Le scavenger la restitue peu à peu au système pour que la mémoire résidente du processus suive l'usage réel du programme. Il se régule pour utiliser environ 1 pour cent du temps CPU et garde un peu plus de mémoire que l'objectif de tas ; une limite mémoire le rend plus agressif.

Avec GODEBUG=scavtrace=1 le runtime affiche l'activité du scavenger, que GC Doctor lit. Si la mémoire résidente reste haute après la fin de la charge, regardez le scavenger et la façon dont le système comptabilise la mémoire rendue.

Documentation officielle : Package runtime: environment variables (GODEBUG gctrace, scavtrace)

Stop the world (Go)

Les deux courtes phases d'un cycle Go où toutes les goroutines sont arrêtées : sweep termination et mark termination.

Un cycle Go a deux phases stop-the-world. Sweep termination, au début, termine le cycle précédent et active la barrière d'écriture. Mark termination, à la fin, termine le marquage et désactive la barrière. Tout ce qui est entre les deux est concurrent. Ce sont les première et troisième valeurs du triplet de temps réel dans gctrace, typiquement de quelques dizaines à quelques centaines de microsecondes.

Une phase stop-the-world longue signifie en général qu'une goroutine n'a pas atteint rapidement un point sûr (une boucle serrée sans appel de fonction, cause classique avant la préemption asynchrone arrivée en Go 1.14) ou que la machine était surchargée. Pendant la phase concurrente le programme continue mais partage les CPU avec le collecteur.