Vol. 9 — № 01
The Go Loop · KiosqueNewsstand
Blog  
Un atelier Go · Édition d'ApprentissageA Go Workshop · Learning Edition

L'escape analysis Escape analysis

Le compilateur décide, seul, si une valeur vit sur la pile (gratuite) ou s'échappe vers le tas (allouée, ramassée plus tard). Retourner &x l'envoie au tas ; une interface peut l'y pousser aussi. -gcflags=-m te montre la décision. The compiler alone decides whether a value lives on the stack (free) or escapes to the heap (allocated, collected later). Returning &x sends it to the heap; an interface can push it there too. -gcflags=-m shows you the decision.

AudienceAudience
Dev qui veut comprendre d'où viennent les allocations et comment lire les décisions du compilateur Dev who wants to understand where allocations come from and how to read compiler decisions
Format
Self-paced
ChapitresChapters
5
Date
Juin 2028 Jun 2028
≈ 18 min ●●●● Escape analysisPileTas

Chapitre 1 en accès libre — la suite (ch. 2 à 5) est réservée. Chapter 1 free to read — the rest (ch. 2–5) is members-only.

01CadrageFraming3 min

La pile est gratuite. Le tas coûte. Le compilateur décide seul — -gcflags=-m te montre ses choix.The stack is free. The heap costs. The compiler decides alone — -gcflags=-m shows you its choices.

Les volumes précédents ont couvert la concurrence, les erreurs, la mémoire partagée. Ce dernier volume descend sous le langage : comment Go gère la mémoire physiquement, où les valeurs vivent, et ce que le runtime fait pendant qu'on travaille. Chaque valeur en Go vit quelque part — sur la pile ou sur le tas. La pile est locale à une goroutine, allouée en bloc au démarrage, libérée automatiquement quand la fonction retourne : son coût est nul. Le tas est partagé entre toutes les goroutines, alloué dynamiquement, et devra être ramassé par le GC : son coût est réel. L'escape analysis est l'analyse statique que le compilateur effectue pour décider, à la compilation, où chaque valeur vivra à l'exécution. La règle de base est simple : une valeur dont la durée de vie ne dépasse pas la frame de la fonction qui la crée peut rester sur la pile. Dès qu'elle doit survivre à cet appel — parce qu'on retourne son adresse, parce qu'on la stocke dans une struct allouée sur le tas, parce qu'une goroutine y accède — elle s'échappe vers le tas. Le compilateur résout cette question seul, sans annotation du programmeur. -gcflags="-m" rend ses décisions visibles : chaque ligne "moved to heap" ou "x escapes to heap" est une allocation que le GC devra éventuellement ramasser.Previous volumes covered concurrency, errors, shared memory. This last volume goes below the language: how Go manages memory physically, where values live, and what the runtime does while you work. Every value in Go lives somewhere — on the stack or the heap. The stack is local to a goroutine, allocated in bulk at startup, freed automatically when the function returns: its cost is zero. The heap is shared across all goroutines, dynamically allocated, and must be collected by the GC: its cost is real. Escape analysis is the static analysis the compiler performs to decide, at compile time, where each value will live at runtime. The basic rule is simple: a value whose lifetime doesn't exceed the frame of the function that creates it can stay on the stack. As soon as it must outlive that call — because you return its address, because you store it in a heap-allocated struct, because a goroutine accesses it — it escapes to the heap. The compiler resolves this question alone, without programmer annotation. -gcflags="-m" makes its decisions visible: each "moved to heap" or "x escapes to heap" line is an allocation the GC will eventually need to collect.

La décision du compilateur : pile ou tas ?The compiler's decision: stack or heap?
// go build -gcflags="-m" ./...
// Le compilateur annote chaque décision d'allocation.

func newPoint(x, y float64) *Point {
    p := Point{x, y}   // p vit ICI — sur la pile de newPoint ?
    return &p           // on retourne son adresse → le compilateur DOIT l'allouer sur le tas
}
// sortie : ./main.go:4:2: moved to heap: p

func sum(a, b int) int {
    return a + b        // résultat retourné par valeur — reste sur la pile
}
// pas d'annotation : rien n'a fui
Pile vs tas — le résuméStack vs heap — the summary
Pile
locale · gratuite · libérée à la sortie de la frame
Tas
partagé · alloué · ramassé par le GC

Le compilateur choisit la pile quand il peut prouver que la durée de vie ne dépasse pas la frame. Dès qu'il ne peut pas prouver, il envoie sur le tas — sûr, mais coûteux.The compiler chooses the stack when it can prove the lifetime doesn't exceed the frame. As soon as it can't prove, it sends to the heap — safe, but costly.

Le réflexe du numéroThe issue's reflex

« Est-ce que cette valeur doit survivre à la fonction qui la crée ? » Si oui, elle ira sur le tas. Ce n'est pas un problème en soi — c'est le comportement attendu pour les valeurs longue durée. Le problème arrive quand une valeur qui n'avait pas besoin de survivre s'échappe quand même — via une interface, via un closure capturant un pointeur, via une goroutine. C'est là que -gcflags="-m" révèle des surprises."Does this value need to outlive the function that creates it?" If yes, it will go to the heap. That's not a problem in itself — it's the expected behavior for long-lived values. The problem comes when a value that didn't need to outlive its function escapes anyway — through an interface, through a closure capturing a pointer, through a goroutine. That's where -gcflags="-m" reveals surprises.

La pile d'une goroutine est dynamiqueA goroutine's stack is dynamic

Contrairement à la plupart des langages, une goroutine Go commence avec une petite pile (2 Ko en Go 1.4+) qui grandit dynamiquement par copie si elle déborde. Pas de pile de taille fixe qui déborde au premier appel profond — la pile s'étend jusqu'à la limite configurée (1 Go par défaut sur 64 bits) ; une récursion infinie finit quand même en « fatal error: stack overflow ». Ce mécanisme de croissance explique pourquoi on peut lancer des millions de goroutines : chacune consomme très peu de mémoire au départ. Mais il a un coût : la copie de pile est une opération non triviale. Les valeurs sur le tas, elles, ne bougent jamais — c'est pourquoi on ne peut pas garder de pointeur brut C vers une valeur Go sur la pile.Unlike most languages, a Go goroutine starts with a small stack (2KB in Go 1.4+) that grows dynamically by copying if it overflows. No fixed-size stack that overflows on the first deep call — the stack extends up to a configured limit (1GB by default on 64-bit); infinite recursion still ends in 'fatal error: stack overflow'. This growth mechanism explains why you can launch millions of goroutines: each consumes very little memory initially. But it has a cost: stack copying is a non-trivial operation. Heap values never move — that's why you can't keep a raw C pointer to a Go value on the stack.

🔒

La suite est réservée The rest is members-only

Le premier numéro est libre. Débloque tout The Go Loop — tous les volumes, à vie — pour 5 €, paiement unique. The first issue is free. Unlock all of The Go Loop — every volume, forever — for €5, one-time.

Retour au kiosqueBack to newsstand