La collecte des déchets (GC) en Go est un mécanisme de gestion de la mémoire automatique, apparu pour la première fois dans les premières versions du langage. Historiquement, le GC en Go a été l'une des sources de critiques en raison de son impact sur les performances. Cependant, avec l'évolution du langage, notamment après la version Go 1.5, il a été considérablement amélioré : un ramasse-miettes concurrent à trois couleurs (concurrent, tricolor, mark-and-sweep) est désormais utilisé, avec des pauses minimales (low-pause GC).
Le problème survient lorsque les programmes créent un grand nombre d'objets temporaires, ou lorsqu'ils ne suppriment pas les références aux structures inutilisées : cela augmente la charge sur le GC et peut entraîner de longues pauses. Une attention particulière doit être accordée aux types d'objets, aux références cycliques et aux longues chaînes de références échappant à la pile.
La solution consiste à surveiller l'allocation de mémoire, à utiliser le profilage et à régler le GC via la variable d'environnement GOGC, en minimisant le nombre d'allocations dans les boucles internes et les sections critiques. Il est important de noter que la collecte des déchets en Go ne s'applique qu'au tas (heap) : tout ce qui est alloué sur la pile sera automatiquement supprimé à la sortie de la portée, tandis que les objets qui "vont" dans le tas sont surveillés par le GC.
Exemple de code :
// Profilage de l'allocation et optimisation du GC import ( "runtime" "fmt" ) func main() { var memStats runtime.MemStats runtime.ReadMemStats(&memStats) fmt.Printf("Avant l'allocation : %d octets ", memStats.Alloc) s := make([]int, 1_000_000) for i := range s { s[i] = i } runtime.GC() // nettoyage manuel runtime.ReadMemStats(&memStats) fmt.Printf("Après le GC : %d octets ", memStats.Alloc) }
Points clés :
GOGC (par exemple, GOGC=100 - standard ; une diminution accélère le GC, mais augmente la consommation de CPU).Comment savoir quel objet "va" dans le tas, et lequel reste sur la pile ?
Réponse : Pour cela, une analyse d'échappement est utilisée, qui peut être analysée à l'aide du drapeau du compilateur go build -gcflags="-m". Les objets qui retournent à l'extérieur de la fonction ou qui sont utilisés dans des fermetures vont souvent dans le tas.
Exemple de code :
func escape() *int { v := 42 return &v // v sera dans le tas }
Le GC affecte-t-il toutes les variables, y compris celles sur la pile ?
Non, le GC ne fonctionne qu'avec le tas (objets alloués sur le heap). Tout ce qui est alloué sur la pile est nettoyé automatiquement lors de la fin de la fonction.
Un appel manuel à runtime.GC() peut-il améliorer considérablement les performances ?
Au contraire, un appel manuel nuit souvent aux performances, augmentant la consommation de CPU. Il ne doit être utilisé que pour des tests ou des situations de débogage.
Cas négatif
Un développeur écrivant un service qui crée de nouveaux grands tableaux à chaque requête, sans réfléchir à leur durée de vie. Cela conduit rapidement à une augmentation de la charge sur le GC, de longues pauses et une baisse des performances.
Avantages :
Inconvénients :
Cas positif
Un développeur optimise le service, profile les allocations, réutilise les tampons via sync.Pool, réduit la fréquence des appels au GC et minimise l'allocation de mémoire dans des zones chaudes.
Avantages :
Inconvénients :