Le GC de Go est concurrent et discret. La bonne réponse n'est presque jamais de le tuner — c'est d'allouer moins.Go's GC is concurrent and quiet. The right answer is almost never to tune it — it's to allocate less.
Le numéro précédent a montré où les valeurs s'allouent. Ce numéro montre ce qui se passe ensuite : le garbage collector ramasse les valeurs sur le tas qui ne sont plus atteignables. Le GC de Go est un collecteur tricolor mark-and-sweep concurrent : il tourne en parallèle avec le programme, en marquant les objets atteignables (blanc → gris → noir) puis en libérant les blancs. Les pauses stop-the-world (STW) sont courtes — quelques dizaines de microsecondes en Go moderne — précisément parce que la majorité du travail se fait de manière concurrente. Le paramètre GOGC contrôle quand un cycle GC se déclenche : GOGC=100 (la valeur par défaut) signifie que le GC se déclenche quand le tas a doublé depuis le dernier cycle. Augmenter GOGC réduit la fréquence des cycles au prix d'une utilisation mémoire plus haute. Baisser GOGC augmente la fréquence des cycles et réduit l'empreinte mémoire. Mais tuner GOGC sans réduire les allocations est un jeu à somme nulle : on déplace la pression, on ne la supprime pas. La vraie leçon est contre-intuitive : le GC de Go est si efficace que la quasi-totalité des problèmes de performance liés au GC se résout en allouant moins, pas en modifiant le GC. Et allouer moins commence par mesurer où les allocations se produisent.The previous issue showed where values get allocated. This issue shows what happens next: the garbage collector collects heap values that are no longer reachable. Go's GC is a concurrent tricolor mark-and-sweep collector: it runs in parallel with the program, marking reachable objects (white → gray → black) then freeing the whites. Stop-the-world (STW) pauses are short — a few tens of microseconds in modern Go — precisely because most work happens concurrently. The GOGC parameter controls when a GC cycle triggers: GOGC=100 (the default) means the GC triggers when the heap has doubled since the last cycle. Increasing GOGC reduces cycle frequency at the cost of higher memory usage. Decreasing GOGC increases cycle frequency and reduces memory footprint. But tuning GOGC without reducing allocations is a zero-sum game: you shift the pressure, you don't eliminate it. The real lesson is counterintuitive: Go's GC is so efficient that virtually all GC-related performance problems are solved by allocating less, not by modifying the GC. And allocating less starts with measuring where allocations happen.
// Voir l'activité du GC en temps réel GODEBUG=gctrace=1 go run ./server.go // Exemple de sortie : // gc 1 @0.012s 1%: 0.014+1.2+0.027 ms clock, 0.11+0.44/1.1/0 ms cpu, 4->4->2 MB, 5 MB goal, 8 P // ^ ^ ^ ^ ^ ^ // numéro depuis STW mark setup + concurrent mark + STW finish heap avant/après/live // du GC le démarrage goal = seuil prochain GC // GOGC=100 (défaut) : GC quand le tas double depuis le dernier GC // GOGC=200 : GC moins fréquent, plus de mémoire utilisée // GOGC=off : GC désactivé (test/benchmark uniquement)
Le GC moderne de Go est concurrent à 99%. Les pauses STW sont de l'ordre de la microseconde, pas de la milliseconde. La latence vient des allocations, pas du collecteur lui-même.Modern Go's GC is 99% concurrent. STW pauses are in the microsecond range, not millisecond. Latency comes from allocations, not from the collector itself.
« Mon programme est lent à cause du GC — je dois tuner GOGC. » Presque toujours faux. Le bon diagnostic est : combien d'allocations par requête ? quelle est leur taille ? d'où viennent-elles ? go tool pprof -alloc_objects et -alloc_space répondent à ces questions. Une fois la source identifiée, on réduit les allocations — sync.Pool, pré-allocation, réutilisation de buffers — et le GC suit naturellement."My program is slow because of the GC — I need to tune GOGC." Almost always wrong. The right diagnosis is: how many allocations per request? what size? where do they come from? go tool pprof -alloc_objects and -alloc_space answer those questions. Once the source is identified, reduce the allocations — sync.Pool, pre-allocation, buffer reuse — and the GC follows naturally.
Go 1.19 a introduit GOMEMLIMIT, une limite souple sur la mémoire totale gérée par le runtime. Contrairement à GOGC qui contrôle la fréquence, GOMEMLIMIT dit « essaie de rester sous X octets » — une cible, pas une garantie : si le tas vivant la dépasse, le programme la dépasse aussi. C'est utile dans des environnements containerisés où la mémoire est limitée : à l'approche de la limite, le GC devient plus agressif pour rester en dessous, ce qui repousse l'OOM. La combinaison recommandée : GOGC=off + GOMEMLIMIT=<mémoire disponible> dans un container. Le GC ne se déclenche que quand la limite approche, réduisant la fréquence — mais si le tas vivant approche la limite, le GC tourne en continu : garder une marge (GOMEMLIMIT ≈ 90 % de la mémoire du container).Go 1.19 introduced GOMEMLIMIT, a soft limit on the total memory managed by the runtime. Unlike GOGC which controls frequency, GOMEMLIMIT says 'try to stay under X bytes' — a target, not a guarantee: if the live heap exceeds it, so does the program. It's useful in containerized environments where memory is limited: as the limit approaches, the GC becomes more aggressive to stay under it, which pushes the OOM back. The recommended combination: GOGC=off + GOMEMLIMIT=<available memory> in a container. The GC only triggers when the limit approaches, reducing frequency — but if the live heap nears the limit, the GC runs nonstop: keep headroom (GOMEMLIMIT ≈ 90% of the container's memory).