0% ont trouvé ce document utile (0 vote)
2 vues26 pages

Advanced

Le document fournit une formation approfondie sur l'optimisation des performances en Go, en se concentrant sur la compréhension du Garbage Collector (GC) et de l'escape analysis. Il aborde les concepts clés tels que la gestion de la mémoire, la réduction des allocations, et l'impact du GC sur la latence, tout en proposant des exercices pratiques pour maîtriser ces compétences. Enfin, il met en avant des techniques avancées et des stratégies pour atteindre un niveau d'expertise dans le développement backend en Go.

Transféré par

Faycel Saoudi
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats DOCX, PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
2 vues26 pages

Advanced

Le document fournit une formation approfondie sur l'optimisation des performances en Go, en se concentrant sur la compréhension du Garbage Collector (GC) et de l'escape analysis. Il aborde les concepts clés tels que la gestion de la mémoire, la réduction des allocations, et l'impact du GC sur la latence, tout en proposant des exercices pratiques pour maîtriser ces compétences. Enfin, il met en avant des techniques avancées et des stratégies pour atteindre un niveau d'expertise dans le développement backend en Go.

Transféré par

Faycel Saoudi
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats DOCX, PDF, TXT ou lisez en ligne sur Scribd

k Si tu veux devenir vraiment pro en Go backend, comprendre le GC et l’escape analysis

te met au-dessus de 80% des devs.

On va structurer ça clairement :

🧠 1️⃣ Garbage Collector en Go — Ce que tu


dois vraiment savoir
Go utilise un GC concurrent, tri-color, mark-and-sweep, optimisé pour faible latence.

🔹 1.1 Les bases indispensables


Tu dois comprendre :

 Heap vs Stack
 Ce qui déclenche une allocation heap
 Comment le GC trace les objets vivants
 Notion de roots
o Variables globales
o Stack
o Registres

Un pro doit savoir répondre à :

Pourquoi mon service consomme 3GB de RAM ?

🔹 1.2 Fonctionnement simplifié du GC


Le GC Go fonctionne en 3 phases :

1. Mark (marquage des objets vivants)


2. Sweep (libération mémoire)
3. Reclaim

Il est :

 Concurrent (tourne pendant ton programme)


 Low-latency
 Non compactant (important)
🔹 1.3 Ce que tu dois maîtriser concrètement
✅ Lire les métriques mémoire
import "runtime"

var m [Link]
[Link](&m)

Tu dois comprendre :

 HeapAlloc
 HeapSys
 NumGC
 PauseTotalNs

✅ Profiling mémoire avec pprof

Indispensable en prod.

go tool pprof

Tu dois savoir :

 Identifier allocations chaudes


 Lire un flamegraph
 Voir qui alloue le plus

✅ GOGC

Variable d’environnement :

GOGC=100

Tu dois comprendre :

 Plus GOGC est élevé → moins de GC → plus de RAM


 Plus GOGC est faible → plus de GC → moins de RAM

En backend haute charge, c’est critique.


🧠 2️⃣ Escape Analysis — Là où les vrais
pros se démarquent
L’escape analysis décide :

Cette variable va sur la stack ou sur le heap ?

Stack = rapide
Heap = lent + GC impact

🔹 2.1 Voir l’escape analysis


Compile avec :

go build -gcflags="-m"

Tu verras :

moved to heap
escapes to heap

Un pro lit ça régulièrement.

🔹 2.2 Quand ça escape ?


Typiquement :

❌ Retourner un pointeur local


func foo() *int {
x := 10
return &x // escape
}

❌ Interface usage
func print(i interface{}) {
[Link](i)
}

Souvent → allocation heap

❌ Closures capturant variables


func main() {
x := 10
go func() {
[Link](x) // escape
}()
}

🔹 2.3 Ce que tu dois savoir optimiser


 Éviter allocations inutiles
 Préférer struct value quand possible
 Éviter conversion excessive vers interface{}
 Réutiliser buffers
 Utiliser [Link] intelligemment

🔥 3️⃣ Niveau Senior — Ce qui impressionne


en entretien
Tu dois comprendre :

🟣 Pourquoi trop d’allocations tuent la perf

Chaque allocation heap :

 Ajoute pression GC
 Augmente latence
 Dégrade throughput

En API à 10k req/s → ça explose vite.

🟣 Write Barrier

Le GC Go utilise une write barrier pour garder la cohérence pendant le mark phase.

Tu n’as pas besoin de l’implémenter,


mais tu dois comprendre que :

Modifier des pointeurs pendant le GC a un coût.

🟣 STW (Stop The World)


Go essaie de minimiser les pauses STW,
mais elles existent.

Tu dois savoir :

 Les mesurer
 Les analyser

🎯 Ce que TU dois maîtriser concrètement


Checklist pro :

 Savoir lire pprof mémoire

 Comprendre Heap vs Stack

 Savoir pourquoi une variable escape

 Optimiser allocations dans hot path

 Comprendre GOGC

 Lire [Link]

 Savoir quand utiliser [Link]

 Comprendre impact GC sur latence

🚀 Mini exercice pour devenir solide


1. Crée une API simple
2. Bombarde-la avec wrk
3. Active pprof
4. Analyse :
o allocations
o GC frequency
o pause times
5. Optimise
6. Mesure avant / après
Ça, c’est niveau senior.

Si tu veux, je peux te faire :

 🧪 Un labo pratique GC étape par étape


 🧠 Une explication ultra deep du tri-color algorithm
 🏆 Comment optimiser une API à 50k req/s
 📊 Un exemple réel de flamegraph analysé

Tu veux qu’on aille jusqu’où ?

les 4

Parfait 😎
Là on rentre dans le territoire senior / performance engineer Go.

Je vais te donner les 4 :

1️⃣ Labo pratique GC


2️⃣ Tri-color algorithm deep dive
3️⃣ Optimiser une API à 50k req/s
4️⃣ Analyse d’un flamegraph réel

🧪 1️⃣ LABO PRATIQUE — Comprendre le


GC en conditions réelles
🎯 Objectif
Voir l’impact des allocations sur :

 Latence
 Throughput
 Fréquence GC
 Pauses STW

Étape 1 — API volontairement mauvaise


package main

import (
"fmt"
"net/http"
)

func handler(w [Link], r *[Link]) {


data := []byte([Link]("Hello %s", [Link]))
[Link](data)
}

func main() {
[Link]("/", handler)
[Link](":8080", nil)
}

Ici :

 [Link] → allocation
 conversion string → []byte → allocation

Étape 2 — Load test


Utilise :

wrk

wrk -t4 -c100 -d30s [Link]

Étape 3 — Activer pprof


Ajoute :

import _ "net/http/pprof"

Puis :

go tool pprof [Link]

Tu dois observer :

 allocations dominées par fmt


 augmentation NumGC
 HeapAlloc qui monte vite

Étape 4 — Optimisation
Version optimisée :

func handler(w [Link], r *[Link]) {


[Link]([]byte("Hello"))
}

Encore mieux :

func handler(w [Link], r *[Link]) {


[Link]([]byte("Hello"))
}

Ou réutiliser un buffer global.

Compare :

 alloc/op
 latency
 GC cycles

🔥 Là tu vois l’impact réel.

🧠 2️⃣ TRI-COLOR MARKING —


Explication Deep
Le GC Go est basé sur l’algorithme tri-color.

3 couleurs :

⚪ Blanc → potentiellement mort


⚫ Noir → vivant confirmé
🔘 Gris → en cours d’analyse

Processus :

1. Tous les objets = blanc


2. Les roots deviennent gris
3. On scanne les gris :
o ils deviennent noirs
o leurs enfants deviennent gris
4. Les blancs restants → supprimés

Pourquoi write barrier ?


Pendant que le GC tourne, ton programme modifie des pointeurs.

La write barrier garantit :


Aucun objet vivant ne reste blanc.

Sans ça → corruption mémoire.

🚀 3️⃣ Optimiser une API à 50k req/s


À ce niveau, les problèmes viennent de :

 Allocations excessives
 JSON encoding
 Logging
 Contention mutex
 GC pressure

🔥 Stratégies pro
1️⃣ Réduire allocations

 Éviter interface{}
 Éviter fmt
 Préallouer slices
 Utiliser [Link]
 [Link] pour gros objets

2️⃣ JSON ultra optimisé

Au lieu de encoding/json classique :

 utiliser [Link] réutilisé


 ou librairie plus rapide

3️⃣ Profiling CPU


go tool pprof [Link]

Cherche :

 encoding/json
 [Link]
 [Link]
Si tu vois [Link] haut → trop d’allocations.

4️⃣ Ajuster GOGC

Par défaut :

GOGC=100

En très haute charge :

GOGC=200

→ Moins de GC
→ Plus de RAM
→ Moins de pauses

📊 4️⃣ Lire un Flamegraph comme un senior


Quand tu génères un flamegraph :

go tool pprof -http=:8081 [Link]

Tu verras :

 Large rectangles = fonctions dominantes


 Large + haut = problème critique

Cas réel typique


Tu vois :

encoding/[Link]

[Link]

[Link]

Conclusion :

 Trop de réflexion
 Trop d’allocations
 GC pressure

Solution :
 Struct simple
 Éviter tags complexes
 Réutiliser buffers

🏆 Niveau Expert — Ce que peu de devs


savent
🔥 Latence P99 impactée par GC

Même si moyenne OK
GC peut tuer ton P99.

Un senior regarde :

 tail latency
 pas seulement average

🔥 Trop de goroutines = pression GC

Chaque goroutine a sa stack.


Beaucoup de goroutines → plus de roots → plus de travail GC.

🔥 [Link] mal utilisé peut empirer

Si objets trop petits → inutile


Si charge instable → inefficace

🎯 Résumé — Si tu maîtrises tout ça


Tu peux :

 Debugger un service Go qui consomme 10GB RAM


 Expliquer pourquoi une API spike en latence
 Optimiser un service à 100k req/s
 Impressionner en entretien senior/staff

Si tu veux on peut maintenant :


 🧬 Simuler une vraie prod avec métriques
 🧱 Construire une API ultra optimisée ensemble
 🧠 Lire le code du runtime GC de Go
 ⚔️Faire un challenge optimisation (je te donne un code pourri à optimiser)

On monte jusqu’à quel niveau ?

encore ue fois les 4

Parfait. On passe en mode staff engineer.


Je vais refaire les 4, mais encore plus profonds, plus réalistes, plus “prod”.

🧪 1️⃣ LABO PRO — Simulation d’un


service qui souffre en prod
🎯 Objectif
Diagnostiquer :

 RAM qui explose


 Latence P99 instable
 GC trop fréquent
 CPU élevé

🧱 Étape 1 — API volontairement toxique


package main

import (
"encoding/json"
"net/http"
)

type User struct {


ID int
Name string
Data []byte
}

func handler(w [Link], r *[Link]) {


users := make([]User, 0)

for i := 0; i < 1000; i++ {


users = append(users, User{
ID: i,
Name: "user",
Data: make([]byte, 1024),
})
}

[Link](w).Encode(users)
}

func main() {
[Link]("/", handler)
[Link](":8080", nil)
}

🔥 Chaque requête :

 1000 structs
 1000 slices de 1KB
 JSON reflection
 Allocations massives

⚡ Étape 2 — Stress test


Utilise :

wrk

wrk -t8 -c500 -d60s [Link]

🔎 Étape 3 — Profiling
Activer :

import _ "net/http/pprof"

Puis :

go tool pprof [Link]

Tu vas voir :

 [Link] énorme
 encoding/json en haut
 GC très fréquent

🧠 Étape 4 — Diagnostic
Problèmes :
1. Trop d’allocations
2. Slice non préallouée correctement
3. Data inutile
4. JSON reflection coûteux

🛠 Étape 5 — Optimisation version senior


 Supprimer Data
 Préallouer slice : make([]User, 1000)
 Réutiliser buffers
 Pagination au lieu de 1000 items

Tu mesures :

 alloc/op
 GC cycles
 P99 latency

👉 Là tu passes du “je code” au “je maîtrise le runtime”.

🧠 2️⃣ TRI-COLOR — Niveau runtime


interne
Go utilise un GC concurrent mark & sweep tri-color non compactant.

🟢 Le vrai problème : mutations pendant le marquage


Pendant la phase mark :

 Le programme continue
 Les pointeurs changent

Sans protection → objets vivants supprimés.

⚔️La Write Barrier


À chaque écriture de pointeur :

[Link] = newValue
Le runtime intercepte et garantit :

Si newValue est blanc → le rendre gris

C’est ce qui maintient l’invariant tri-color :

Aucun objet noir ne pointe vers un blanc.

🛑 STW (Stop The World)


Même si Go est concurrent :

Il y a 2 STW courts :

 Début du mark
 Fin du mark

En haute charge :

 Beaucoup de roots
 Beaucoup de goroutines
→ STW plus long

🧬 Ce que peu savent


 Plus tu as de goroutines → plus le GC scanne de stacks
 Chaque goroutine = root
 Les stacks grandissent dynamiquement

Donc :

100k goroutines = coût GC réel.

🚀 3️⃣ Optimiser une API à 50k req/s


(réellement)
À ce niveau, tu dois penser :

 Allocation budget par requête


 CPU cycles
 GC budget
 Tail latency

🎯 Budget cible réaliste


Pour 50k req/s :

 < 1–2 allocs par requête


 < 200 bytes alloc/request
 GC stable

🔥 Techniques avancées
1️⃣ Struct packing

Ordre des champs :

type A struct {
ID int64
Flag bool
}

Peut générer padding.

Optimiser l’ordre réduit mémoire.

2️⃣ Éviter interface{}

Chaque conversion peut provoquer escape.

3️⃣ Reuse via [Link]

Bon pour :

 Buffers
 Gros objets temporaires

Mauvais pour :

 Petits objets
 Objets courts
4️⃣ Réduction JSON cost

encoding/json est lent car :

 reflection
 allocations

Solutions :

 encoder manuel
 reuse buffer
 streaming

5️⃣ Ajuster GOGC intelligemment

Exemple :

GOGC=200

Moins de GC
Plus de RAM
Moins de pauses

Mais si RAM limitée → dangereux.

📊 4️⃣ Lecture d’un flamegraph réel


Commande :

go tool pprof -http=:8080 [Link]

🔎 Cas typique 1
[Link]

→ Trop d’allocations

🔎 Cas typique 2
encoding/[Link]

→ Reflection lourde

🔎 Cas typique 3
[Link]

→ Trop d’objets heap

🔎 Cas typique 4
[Link]

→ GC trop fréquent

🎯 Ce qu’un senior fait


Il ne regarde PAS seulement :

 alloc_space

Il regarde :

 inuse_space
 P99 latency
 Corrélation GC spike ↔ latency spike

🏆 Niveau Staff — Ce que 99% ne savent pas


🔥 La latence moyenne ne sert à rien

Toujours analyser :

 P95
 P99
 Max
🔥 Un service peut être CPU-bound ET GC-bound

Les deux peuvent se combiner.

🔥 Trop optimiser peut empirer

Exemple :

 [Link] mal dimensionné


 contention mutex
 false sharing

🧠 Si tu maîtrises vraiment ces 4


Tu peux :

 Debugger une fuite mémoire


 Expliquer un spike P99
 Réduire RAM d’un service de 40%
 Doubler throughput sans changer hardware

Maintenant dis-moi :

On passe à quoi ?

 🧬 Lire le code du GC dans le runtime Go


 🧠 Faire un vrai challenge optimisation
 ⚔️Simuler une fuite mémoire distribuée
 🚀 Construire une API ultra low-allocation ensemble

On continue jusqu’au niveau “Google SRE” ?

toujours les 4

Excellent 😈
On refait les 4, mais cette fois au niveau “je peux debugger un service Go en prod à 3h du
matin”.

On va faire :

1️⃣ Simulation d’un incident réel


2️⃣ GC interne encore plus deep
3️⃣ Optimisation hardcore 50k+ req/s
4️⃣ Lecture avancée de flamegraph (corrélation GC ↔ latence)

1️⃣ 🧪 INCIDENT PROD RÉALISTE


🚨 Symptômes
 RAM passe de 500MB → 6GB en 20 min
 P99 passe de 20ms → 800ms
 CPU OK
 Pas de panic

Tu es on-call.

🔎 Étape 1 — Vérifier les stats runtime


Ajoute temporairement :

import (
"log"
"runtime"
"time"
)

func monitor() {
for {
var m [Link]
[Link](&m)
[Link]("HeapAlloc=%dMB NumGC=%d PauseTotal=%dms\n",
[Link]/1024/1024,
[Link],
[Link]/1e6)
[Link](5 * [Link])
}
}

Ce que tu observes :

 HeapAlloc monte en continu


 NumGC augmente
 PauseTotal augmente

👉 Donc ce n’est pas une absence de GC


👉 C’est une pression allocation constante
🔬 Étape 2 — Heap profile
go tool pprof [Link]

Tu vois :

encoding/[Link]
[Link]

Conclusion :

Chaque requête alloue massivement.

🎯 Étape 3 — Hypothèse
Quelqu’un a ajouté :

[Link](bigStruct)

bigStruct contient :

 Gros slices
 Nested maps
 Interfaces

🔥 Résultat :

 allocations massives
 pression GC
 latence P99 détruite

2️⃣ 🧠 GC INTERNE — VERSION


RUNTIME ENGINEER
Le GC Go est :

 Concurrent
 Incremental
 Non compactant
 Tri-color
 Hybrid write barrier
🧬 Ce que peu savent : Pacing algorithm
Le GC n’attend pas que la mémoire explose.

Il calcule :

next_gc = heap_live * (1 + GOGC/100)

Si GOGC=100
→ GC déclenché quand heap double.

🔥 Pourquoi trop d’allocations tue la perf


Chaque allocation :

1. Passe par [Link]


2. Peut déclencher un assist GC

🧨 GC Assist (peu connu)


Quand le programme alloue trop vite :

Le mutator (ton code) est forcé d’aider le GC.

Donc :

Ton handler HTTP fait du travail GC.

Ça détruit la latence.

3️⃣ 🚀 Optimiser réellement 50k req/s


On vise :

 < 1 alloc / request


 Stable heap
 P99 stable

🔥 Technique 1 — Zéro allocation string


❌ Mauvais :

[Link]("user-%d", id)

✅ Bon :

buf := make([]byte, 0, 32)


buf = append(buf, "user-"...)
buf = [Link](buf, int64(id), 10)

🔥 Technique 2 — Éviter map[string]interface{}


Chaque interface = potentiel escape.

🔥 Technique 3 — Préallocation stricte


users := make([]User, 1000) // pas 0 capacity

🔥 Technique 4 — Batch processing


Au lieu de 1000 objets par requête
→ pagination

Moins d’objets heap


Moins de GC roots
Moins de scan

🔥 Technique 5 — Limiter goroutines


100k goroutines =

 100k stacks
 100k roots
 Scan GC massif

Un worker pool est souvent meilleur.

4️⃣ 📊 Flamegraph — Lecture Expert


Commande :
go tool pprof -http=:8080 [Link]

🎯 Cas A — [Link] large


→ Trop d’objets heap
→ Structure trop fragmentée

Solution :

 Réduire nested structs


 Réduire pointeurs

🎯 Cas B — [Link]
🔥 Très mauvais signe

Signifie :

Ton code aide le GC car il alloue trop vite.

🎯 Cas C — [Link]
GC sweep lourd
→ Heap trop gros
→ GOGC mal réglé

🎯 Corrélation Pro
Tu dois corréler :

 GC cycle timestamp
 Spike latence
 Heap growth

Un senior :

Ne regarde jamais un seul graphique.

Il croise :

 Heap
 CPU
 Goroutines
 P99
 GC cycles

🏆 Ce que tu dois savoir expliquer en


entretien Staff
1. Pourquoi le GC Go n’est pas compactant
2. Pourquoi trop de goroutines impacte le GC
3. Ce qu’est GC assist
4. Comment réduire alloc/op
5. Pourquoi P99 est plus important que moyenne
6. Comment tuning GOGC affecte latence

🔥 Si tu maîtrises vraiment ces 4


Tu peux :

 Diviser RAM par 3


 Stabiliser P99 sous charge
 Debugger fuite mémoire
 Expliquer [Link] dans un flamegraph
 Concevoir API ultra low-allocation

Maintenant on peut aller encore plus loin :

 🧬 Lire directement le code du GC dans le runtime Go


 ⚔️Faire un challenge d’optimisation chronométré
 🧠 Simuler une vraie fuite mémoire subtile
 🚀 Construire un microservice ultra low-latency ensemble

Tu veux encore les 4 ? 😈

Vous aimerez peut-être aussi