Introduction : Pourquoi la performance de Go est importante
Go (Golang) est un langage compilé à typage statique conçu pour les systèmes à forte charge. Cependant, même avec Go, vous pouvez écrire une application lente si vous ne tenez pas compte des spécificités du fonctionnement du ramasse-miettes (GC), de la gestion de la mémoire et de la concurrence. Dans cet article, nous allons détailler les techniques d'optimisation clés : du profilage au réglage fin du GC. Vous apprendrez à transformer un code "brut" en une solution performante prête pour la production.
1. Profilage : Trouver les goulots d'étranglement
La première étape de l'optimisation est le diagnostic. Go fournit le package intégré net/http/pprof pour collecter les profils CPU, mémoire, verrous et goroutines.
Démarrer le profilage
Ajoutez dans main.go :
import ( _ "net/http/pprof" "net/http")
func main() { go func() { http.ListenAndServe("localhost:6060", nil) }() // votre code principal}Exécutez ensuite :go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
Analyser le profil CPU
Après avoir collecté les données, une console interactive s'ouvre. Utilisez les commandes :
top10— affiche les 10 fonctions les plus "chaudes" ;web— visualise le graphe d'appels dans le navigateur ;list <nom_fonction>— affiche l'utilisation du CPU ligne par ligne.
Exemple de sortie :
Showing nodes accounting for 80.60s, 85.12% of 94.70s total flat flat% sum% cum cum% 20.30s 21.44% 21.44% 20.30s 21.44% runtime.memmove 15.10s 15.94% 37.38% 15.10s 15.94% runtime.mallocgcSi vous voyez runtime.memmove ou mallocgc en haut — le problème est une allocation mémoire fréquente.
2. Gestion de la mémoire et GC
Le ramasse-miettes en Go est "stop-the-world", mais depuis Go 1.5, il est devenu parallèle. Néanmoins, les allocations fréquentes augmentent la charge sur le GC.
Éviter les allocations inutiles
Mauvais exemple :
func sum(values []int) int { total := 0 for _, v := range values { total += v } // la slice est passée par référence, mais si vous créez une copie — c'est une allocation return total}Bon exemple — utilisez des canaux bufferisés et des pools d'objets :
var bufferPool = sync.Pool{ New: func() interface{} { return make([]byte, 1024) },}
func process() { buf := bufferPool.Get().([]byte) defer bufferPool.Put(buf) // travailler avec buf}Réglage du GC
La variable d'environnement GOGC contrôle la fréquence de collecte. La valeur par défaut est 100 (la collecte démarre lorsque la taille du tas double). Augmentez-la à 200–500 pour les applications avec de nombreuses allocations :
export GOGC=200./monappMais attention : un GOGC trop élevé peut entraîner une consommation mémoire de pointe.
3. Concurrence : Goroutines et Canaux
Go est réputé pour ses goroutines légères, mais une utilisation incorrecte entraîne des fuites et des interblocages.
Limiter le nombre de goroutines
Utilisez un pool de workers :
func workerPool(tâches []int, nbWorkers int) { tâchesChan := make(chan int, len(tâches)) résultats := make(chan int, len(tâches))
for w := 0; w < nbWorkers; w++ { go func() { for tâche := range tâchesChan { résultats <- traiterTâche(tâche) // travail lourd } }() }
for _, j := range tâches { tâchesChan <- j } close(tâchesChan)
for i := 0; i < len(tâches); i++ { <-résultats }}Éviter le blocage et les interblocages
Utilisez select avec des timeouts :
select {case res := <-résultatChan: return rescas <-time.After(5 * time.Second): return nil