web-dev-qa-db-fra.com

Comment analyser la mémoire de golang?

J'ai écrit un programme golang, qui utilise 1,2 Go de mémoire au moment de l'exécution.

Appeler go tool pprof http://10.10.58.118:8601/debug/pprof/heap résulte en un vidage avec seulement 323,4 Mo d’utilisation du tas.

  • Qu'en est-il du reste de l'utilisation de la mémoire?
  • Existe-t-il un meilleur outil pour expliquer la mémoire d'exécution golang?

En utilisant gcvis je comprends ceci:

enter image description here

.. et ce profil de forme de tas:

enter image description here

Voici mon code: https://github.com/sharewind/Push-server/blob/v3/broker

61
sharewind

Le profil de tas montre la mémoire active, la mémoire que le runtime croit être utilisée par le programme go (c'est-à-dire: n'a pas été collectée par le garbage collector). Lorsque le CPG collecte la mémoire, le profil est réduit, mais aucune mémoire n'est restituée au système . Vos allocations futures essaieront d'utiliser la mémoire du pool d'objets précédemment collectés avant d'en demander plus au système.

De l'extérieur, cela signifie que l'utilisation de la mémoire de votre programme augmentera ou restera égale. Ce que le système extérieur présente comme la "taille de résident" de votre programme est le nombre d'octets de RAM est affecté à votre programme, qu'il contienne des valeurs en cours d'utilisation ou des valeurs collectées.

La raison pour laquelle ces deux chiffres sont souvent très différents est la suivante:

  1. La mémoire de collecte du GC n'a aucun effet sur la vue extérieure du programme
  2. Fragmentation de la mémoire
  3. Le catalogue global ne fonctionne que lorsque la mémoire utilisée double la mémoire utilisée après le catalogue précédent (par défaut, voir: http://golang.org/pkg/runtime/#pkg-overview )

Si vous voulez une ventilation précise de la mémoire de Go, vous pouvez utiliser l'appel runtime.ReadMemStats: http://golang.org/pkg/runtime/#ReadMemStats

Sinon, puisque vous utilisez le profilage Web, vous pouvez accéder aux données de profilage via votre navigateur à l'adresse: http://10.10.58.118:8601/debug/pprof/ _, en cliquant sur le lien du segment, vous verrez la vue de débogage du profil du segment, qui affiche une structure runtime.MemStats en bas.

La documentation de runtime.MemStats ( http://golang.org/pkg/runtime/#MemStats ) contient l'explication de tous les champs, mais les plus intéressants pour cette discussion sont les suivants:

  • HeapAlloc: essentiellement ce que le profileur vous donne (mémoire active)
  • Alloc: similaire à HeapAlloc, mais pour toutes les mémoires gérées
  • Sys: la quantité totale de mémoire (espace adresse) demandée au système d'exploitation

Il y aura toujours des divergences entre Sys et ce que le système d'exploitation rapporte, car ce que Go demande au système et ce que le système d'exploitation lui donne ne sont pas toujours les mêmes. De plus, la mémoire CGO/syscall (par exemple: malloc/mmap) n'est pas suivie par go.

53
Cookie of Nine

En complément de la réponse de @Cookie of Nine, vous pouvez essayer le --alloc_space option.

go tool pprof utilisation --inuse_space par défaut. Il échantillonne l'utilisation de la mémoire afin que le résultat soit un sous-ensemble du nombre réel.
Par --alloc_space pprof retourne toute la mémoire allouée depuis le début du programme.

25
menghan

J'étais toujours perplexe devant la mémoire résidentielle croissante de mes applications Go et j'ai finalement dû apprendre les outils de profilage présents dans l'écosystème Go. Runtime fournit de nombreuses métriques au sein d'une structure runtime.Memstats , mais il peut être difficile de comprendre lequel d'entre elles peut aider à comprendre les raisons de la croissance de la mémoire; certains outils supplémentaires sont donc nécessaires.

Environnement de profilage

Utilisez https://github.com/tevjef/go-runtime-metrics dans votre application. Par exemple, vous pouvez mettre ceci dans votre main:

import(
    metrics "github.com/tevjef/go-runtime-metrics"
)
func main() {
    //...
    metrics.DefaultConfig.CollectionInterval = time.Second
    if err := metrics.RunCollector(metrics.DefaultConfig); err != nil {
        // handle error
    }
}

Exécutez InfluxDB et Grafana dans les conteneurs Docker:

docker run --name influxdb -d -p 8086:8086 influxdb
docker run -d -p 9090:3000/tcp --link influxdb --name=grafana grafana/grafana:4.1.0

Configurer l'interaction entre Grafana et InfluxDBGrafana (page principale de Grafana -> Angle supérieur gauche -> Sources de données -> Ajouter un nouveau source de données):

enter image description here

Tableau de bord d'importation # 3242 de https://grafana.com (Page principale de Grafana -> Coin supérieur gauche -> Tableau de bord -> Importer):

enter image description here

Enfin, lancez votre application: elle transmettra des métriques d’exécution au Influxdb contenerisé. Mettez votre demande sous une charge raisonnable (dans mon cas, elle était assez petite - 5 RPS pour plusieurs heures).

Analyse de la consommation de mémoire

  1. La courbe Sys (le synonyme de RSS) est assez similaire à la courbe HeapSys. Il s’avère que l’allocation dynamique de la mémoire est le principal facteur de la croissance globale de la mémoire, de sorte que la faible quantité de mémoire consommée par les variables de pile semble être constante et peut être ignorée;
  2. La quantité constante de goroutines garantit l'absence de fuite de goroutine/fuite des variables de pile;
  3. La quantité totale d'objets alloués reste la même (il est inutile de prendre en compte les fluctuations) pendant la durée de vie du processus.
  4. Le fait le plus surprenant: HeapIdle croît au même taux qu'un Sys, tandis que HeapReleased est toujours égal à zéro . Évidemment, l'exécution ne renvoie pas la mémoire à l'OS du tout, du moins dans les conditions de ce test:
HeapIdle minus HeapReleased estimates the amount of memory    
that could be returned to the OS, but is being retained by
the runtime so it can grow the heap without requesting more
memory from the OS.

enter image description hereenter image description here

Pour ceux qui essaient d’examiner le problème de la consommation de mémoire, je vous recommande de suivre les étapes décrites afin d’exclure certaines erreurs triviales (comme une fuite de goroutine).

Libérer explicitement la mémoire

Il est intéressant de noter que celui-ci peut considérablement réduire la consommation de mémoire avec des appels explicites à debug.FreeOSMemory():

// in the top-level package
func init() {
   go func() {
       t := time.Tick(time.Second)
       for {
           <-t
           debug.FreeOSMemory()
       }
   }()
}

comparison

En fait, cette approche a permis d’économiser environ 35% de la mémoire par rapport aux conditions par défaut.

13
Vitaly Isaev

Vous pouvez également utiliser StackImpact , qui enregistre et signale automatiquement les profils d'allocation de mémoire réguliers et déclenchés par des anomalies dans le tableau de bord, disponibles sous une forme historique et comparable. Voir ce billet de blog pour plus de détails Détection de fuites de mémoire dans les applications en production

enter image description here

Disclaimer: Je travaille pour StackImpact

5
logix