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.
En utilisant gcvis
je comprends ceci:
.. et ce profil de forme de tas:
Voici mon code: https://github.com/sharewind/Push-server/blob/v3/broker
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:
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:
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.
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.
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 InfluxDB
Grafana
(page principale de Grafana -> Angle supérieur gauche -> Sources de données -> Ajouter un nouveau source de données):
Tableau de bord d'importation # 3242 de https://grafana.com (Page principale de Grafana -> Coin supérieur gauche -> Tableau de bord -> Importer):
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
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;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.
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()
}
}()
}
En fait, cette approche a permis d’économiser environ 35% de la mémoire par rapport aux conditions par défaut.
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
Disclaimer: Je travaille pour StackImpact