2. Pile (Stack) : Zéro Allocation
La pile d'exécution est le stockage le plus rapide et le plus efficient pour les fonctions :
- Allocation Instantanée : Déplacement mécanique du pointeur de pile (
RSP), coût O(1) de zéro cycle additionnel. - Libération Automatique : Restitution immédiate de la mémoire au retour de la fonction (function return).
- Zéro Garbage Collection : Aucun travail d'analyse ni interruption pour le ramasse-miettes.
- Localité Maximale : Le sommet de pile réside en permanence dans le cache L1 ultra-rapide.
3. Le Tas (Heap) & Pression GC
Le tas est un espace mémoire dynamique partagé entre l'ensemble des goroutines et threads :
Recherche d'un bloc adapté, mise à jour des métadonnées et risque de fragmentation.
Gestion de verrous et tampons locaux pour allouer de la mémoire partagée sans conflit.
Chaque objet alloué sur le tas doit être tracé, scanné et libéré par le ramasse-miettes.
4. Stack vs Heap : Comparatif
Synthèse comparative des deux modes d'allocation mémoire du runtime :
Durée de vie liée à la fonction. Zéro overhead, localité L1 maximale, zéro intervention GC.
Durée de vie dynamique dépassant la fonction. Coût d'allocation et charge de balayage GC.
Maintenir les chemins critiques en zéro-allocation sur le tas : 0 B/op et 0 allocs/op.
Analyse d'Échappement
5. Analyse d'Échappement du Compilateur
Le compilateur analyse statiquement le code pour décider du placement mémoire optimal :
6. Les 4 Causes d'Échappement
Les scénarios classiques qui provoquent une allocation silencieuse sur le tas :
- Retour de pointeur : Retourner l'adresse d'une variable locale
return &user. - Assignation à une interface (
any) : Provoque la mise en boîte (boxing) dynamique. - Tranches dynamiques :
make([]byte, size)dont la taille n'est pas une constante à la compilation. - Capture dans une goroutine : Variable transmise par pointeur à une fonction asynchrone
go func().
7. Diagnostic : Drapeaux d'Échappement
Pour inspecter les décisions du compilateur Go en ligne de commande :
1# Affiche les décisions d'inlining et d'échappement
2go build -gcflags="-m" ./...
3
4# Niveau de détail maximal avec le cheminement complet
5go build -gcflags="-m -m" ./...
- Sorties caractéristiques :
moved to heap: user: La variable a été déplacée sur le tas.user does not escape: Optimisation réussie, la variable reste sur la pile.
Organisation & Alignement des Données
8. Alignement Mémoire Matériel
Sur une architecture 64 bits, chaque donnée doit être alignée sur une adresse multiple de sa taille :
- 8 octets :
int64,float64, pointeurs (adresses multiples de 8). - 4 octets :
int32,float32(adresses multiples de 4). - 2 octets :
int16(adresses multiples de 2). - 1 octet :
bool,byte,int8(toutes adresses).
Si les champs d'une structure ne sont pas ordonnés, le compilateur insère des octets vides invisibles pour aligner les adresses matérielles.
9. Principes d'Alignement & Padding Mémoire
Sur architecture 64-bit, le processeur lit et écrit dans la mémoire par mots alignés de 8 octets. Le compilateur applique des contraintes matérielles strictes : chaque champ doit débuter à une adresse multiple de sa taille naturelle.
Un type 8-bit (int64, pointeur, string) exige une adresse multiple de 8. Un type 4-bit (int32) exige un multiple de 4.
Si les champs sont déclarés sans ordre, le compilateur insère des octets vides pour respecter l'alignement, gaspillant 20% à 40% de RAM.
Ordonner toujours les champs du plus grand au plus petit (8B → 4B → 2B → 1B) pour agréger les petits types sans aucun trou.
Détecter et corriger automatiquement vos structures avec l'analyseur Go officiel :
go vet -vettool=$(which fieldalignment) ./...
10. Cas Pratique : Structure Désordonnée vs Compactée
Comparaison de l'empreinte mémoire d'une même structure avant et après réorganisation des champs :
type CandidateBad struct {
Found bool // 1B + 7B pad (aligné 8B)
Attempts int64 // 8B
CharsetID byte // 1B + 3B pad (aligné 4B)
Length int32 // 4B
Target string // 16B (Ptr 8B + Len 8B)
} // Total = 40 octets (10B de vide inutile !)
type CandidateGood struct {
Attempts int64 // 8B
Target string // 16B (Ptr 8B + Len 8B)
Length int32 // 4B
Found bool // 1B
CharsetID byte // 1B + 2B pad final
} // Total = 32 octets (-20% de RAM !)
11. Le Piège du Faux Partage
Quand deux cœurs écrivent sur deux variables indépendantes situées sur la même ligne de 64 octets :
Le protocole matériel de cohérence de cache invalide en continu la ligne de cache entre les deux cœurs.
Chaque écriture force un aller-retour vers la mémoire centrale : le débit s'effondre sans qu'aucun verrou logiciel ne soit posé.
1// PIÈGE : Deux compteurs distincts sur la même ligne de 64 octets
2type BadCounters struct {
3 Core0Count uint64 // 8 octets (offset 0)
4 Core1Count uint64 // 8 octets (offset 8) -> Même Cache Line !
5}
12. Résoudre le Faux Partage
Pour éliminer l'invalidation matérielle, on sépare les variables par du remplissage (padding) :
1// SOLUTION : Isoler chaque compteur sur sa propre ligne de 64 octets
2type PaddedCounter struct {
3 value uint64
4 _ [7]uint64 // 56 octets de remplissage (total = 64 octets)
5}
6
7type SafeCounters struct {
8 Core0 PaddedCounter // Cache Line #1 dédiée
9 Core1 PaddedCounter // Cache Line #2 dédiée
10}
- Chaque cœur modifie exclusivement sa propre ligne de cache sans interférer avec les autres.
- Les gains mesurés atteignent souvent x5 à x10 en situation de forte concurrence.
13. Synthèse Modèle Mémoire
Les 3 règles d'or pour la gestion mémoire du runtime :
Éviter les retours de pointeurs inutiles pour laisser le compilateur allouer sur la pile sans GC.
Inspecter régulièrement avec -gcflags="-m" pour traquer les boxing et allocations silencieuses.
Trier les champs par taille décroissante et isoler les compteurs concurrents par padding de 64 octets.
TP Fil Rouge (Séance 3) : Zéro-Allocation & Struct Padding
Mission : Analyser les décisions d'échappement du compilateur pour éliminer toutes les allocations sur le Tas, et compacter la structure Candidate de 40 à 32 octets par field alignment.
1. Diagnostic Escape Analysis
Inspecter les choix du compilateur avec go build -gcflags="-m". Constater que les conversions en string et concaténations allouent des millions d'objets sur le Tas.
2. Compactage de Structure (Padding)
Réorganiser les champs de Candidate par ordre de taille décroissante pour éliminer 8 octets de padding invisible et passer de 40 octets à 32 octets nets.
3. Buffers Fixes sur la Pile
Remplacer les allocations dynamiques et concaténations par des tableaux d'octets contigus fixes sur la Stack ([8]byte) avec mutation directe d'index.
4. Validation 0 allocs/op
Valider avec go test -bench . -benchmem que la boucle critique atteint strictement 0 B/op et 0 allocs/op avec un débit maximal.