essais et erreurs pour améliorer le profilage de la mémoire cgo par les débutants

Cet article est l'article du 12ème jour du calendrier de l'avent des ingénieurs freee.

Bonjour. Je m'appelle @taiyo et je suis ingénieur stagiaire avec 17 diplômés de freee. Je n'ai touché que les langages de script, tels que Ruby pour freee, PHP pour les cercles et Python pour la thèse de fin d'études. J'ai eu l'opportunité de m'impliquer dans le développement en utilisant go in freee et j'ai commencé à y toucher, et j'ai commencé à aimer go il y a environ 3 mois.

Je suis encore un nouvel ingénieur depuis environ 1 an et 3 mois, mais j'ai été déclenché par les événements décrits plus loin. Comme "** je veux faciliter le profilage de la mémoire du côté C avec des applications go-made utilisant cgo **" a augmenté.

Je voudrais écrire sur ces trois points.

J'ai une fuite de mémoire sur mon serveur Go API

C'est le début du problème. Après avoir publié une modification majeure du serveur Go API, une fuite de mémoire s'est produite quelques jours plus tard. Je me suis réveillé ...! !!

Comme mentionné précédemment, je n'ai jamais rencontré le phénomène des fuites de mémoire en touchant uniquement les langages de script, et c'était ma première expérience, donc j'étais incroyablement impatient. Je ne savais pas quoi faire, mais j'ai continué avec des conseils pendant que j'étais au chaud.

Se trouve être la cause de fuites de mémoire dans Go et C

En conclusion, la cause a été trouvée à la fois du côté Go et du côté C.

Première enquête

L'application contenait la bibliothèque standard de Go net / http / pprof, donc Je l'ai utilisé pour vérifier l'utilisation de la mémoire. Au cours de mes recherches, je me souviens avoir simplement dit "Wow!" Pour la commodité de net / http / pprof et la grandeur de go, qui est implémentée comme une bibliothèque standard.

Après tout, dans l'enquête ici, ** CString of cgo qui n'est pas la cible de GC of go, Données que je pensais pouvoir publier Il s'est avéré que l'une des causes était que ce n'était pas publié **.

Ajouter du code pour libérer ↓,

defer C.free(unsafe.Pointer(released))

Corrigez et relâchez immédiatement! Cela devrait le réparer!

... j'ai pensé, mais il y avait encore une fuite de mémoire.

Deuxième enquête

Je n'ai pas trouvé de cause possible lorsque j'ai cherché à l'aide de pprof. Comme cette application utilise cgo, j'ai décidé d'examiner le code C et de séparer la partie d'implémentation ** C de Go et de mettre valgrind de ** À la suite de l'enquête, j'ai trouvé une pièce qui semble être un goulot d'étranglement de fuite du côté C.

Je l'ai réparé et relâché, puis la fuite ne s'est pas produite, donc la situation a été réglée pour le moment.

J'ai pensé comme ça

Je veux pouvoir faire de la recherche et de la recherche C ensemble

Dans la première enquête, net / http / pprof est incroyable! !! J'ai pensé, mais il y avait un piège inattendu. La cible de profilage de net / http / pprof est uniquement la mémoire allouée par go, ** C ne semble pas afficher **.

De plus, bien qu'il s'agisse d'un valgrind majeur dans le profileur de mémoire C et C ++, il semble qu'il ne soit pas pris en charge par Go et cgo jusqu'à présent (https://github.com/golang/go/issues/782), comme mentionné ci-dessus. J'ai dû enquêter sur le ** code C séparément de Go **.

Cela nécessiterait des enquêtes ** go et C distinctes **, ce qui prendrait du temps. Au lieu de faire cela, vous voulez trouver un goulot d'étranglement en un seul coup.

Ce serait bien de pouvoir voir l'état de la mémoire du côté C avec net / http / pprof

Je souhaite également créer un environnement qui permette d'enquêter facilement lorsque la même chose se produit. net / http / pprof Après tout, c'est pratique, donc ce serait bien de pouvoir afficher quelle ligne de C saisit la mémoire avec la même interface.

Puisqu'il y a cgo, la compatibilité entre go et C devrait être élevée, donc je me demande si je peux faire quelque chose à ce sujet. J'ai donc décidé d'essayer diverses choses.

politique

Les trois suivants sont des candidats.

  1. Aligner les allocateurs Go et C
  2. Créez une bibliothèque qui profilera C à partir de Go avec la même interface que net / http / pprof
  3. Faites de votre mieux avec valgrind (maintenez le statu quo)

Quant à 2, je pensais que cela prendrait beaucoup de temps et de force, alors Cette fois, je me suis concentré sur la question de savoir si le profilage peut être fait à partir de pprof par «Aligning Go and C allocators» dans 1.

Hypothèse pour aligner les allocateurs go et C

Pour le moment, j'ai suivi le code source de net / http / pprof. Finalement, nous sommes arrivés à runtime / malloc.go

runtime/malloc.go


// Memory allocator.
//
// This was originally based on tcmalloc, but has diverged quite a bit.
// http://goog-perftools.sourceforge.net/doc/tcmalloc.html

// The main allocator works in runs of pages.
// Small allocation sizes (up to and including 32 kB) are
// rounded to one of about 70 size classes, each of which
// has its own free set of objects of exactly that size.
// Any free page of memory can be split into a set of objects
// of one size class, which are then managed using a free bitmap.
//
// The allocator's data structures are:
//
//	fixalloc: a free-list allocator for fixed-size off-heap objects,
//		used to manage storage used by the allocator.
//	mheap: the malloc heap, managed at page (8192-byte) granularity.
//	mspan: a run of pages managed by the mheap.
//	mcentral: collects all spans of a given size class.
//	mcache: a per-P cache of mspans with free space.
//	mstats: allocation statistics.

Comme vous pouvez le voir dans ce commentaire, ** go base son allocation de mémoire sur tcmalloc, ce qui est normal en C. J'utilisais malloc. ** ** À partir de là, si les allocateurs utilisés pour l'allocation de mémoire sont différents, s'ils sont identiques, ils seront bien pris. J'ai posé l'hypothèse.

En fait, à l'origine, "Pourquoi ne pas combiner les différents allocateurs utilisés entre C et Go?" On m'a conseillé. Cependant, à ce moment-là, honnêtement, je ne le comprenais pas vraiment, et après avoir lu le code jusqu'à présent, j'avais l'impression d'être enfin connecté. Pour le moment, testons l'hypothèse "essayer de faire correspondre l'allocateur C avec tcmalloc".

Remplacé par gperftools

Le remplacement par malloc-> tcmalloc est le plus souvent trouvé dans les recherches, et dans l'espoir qu'il sera compatible avec go made by google, gperftools Je l'ai essayé. (J'étais un peu inquiet qu'il n'y avait pas un tout nouvel article sur gperftools, mais le commit du dépôt github était le dernier en 2016/11.)

Cette fois, nous avons introduit gperftools dans la version de l'application go qui a causé la fuite de mémoire et confirmé l'opération. L'environnement de développement est construit sur le conteneur à l'aide de docker.

Voici le code supplémentaire. (Simplifié)

#Dockerfile

...
RUN apt-get -y install google-perftools libgoogle-perftools-dev
...

cgo_sample.go


package main

/*
...
#cgo CFLAGS: -I/usr/include
#cgo LDFLAGS: -L/usr/lib -ltcmalloc
#include "gperftools/tcmalloc.h"
*/
import "C"
import (
  "net/http"
  _ "net/http/pprof"
);

func main() {
  http.HandleFunc("/", handler)
  http.ListenAndServe(":8800", nil)
}

func handler(w http.ResponseWriter, r *http.Request) {
  //Traitement qui fuit en C, etc.
}

Installez google-perftools et libgoogole-perftools-dev avec apt-get. Les deux installés par apt-get sont sous / usr, alors spécifiez-les respectivement dans CFLAGS et LDFLAGS.

résultat de l'inspection

$ curl http://localhost:8800
$ go tool pprof http://localhost:8800/debug/pprof/heap?debug=1
(pprof) text
5836.79kB of 5836.79kB total (  100%)
Dropped 57 nodes (cum <= 29.18kB)
Showing top 10 nodes out of 23 (cum >= 512.69kB)
      flat  flat%   sum%        cum   cum%
 2430.03kB 41.63% 41.63%  2430.03kB 41.63%  go.SomeLeakMethod1
 1334.04kB 22.86% 64.49%  1334.04kB 22.86%  go.OtherLeakMethod1
 1048.02kB 17.96% 82.44%  1048.02kB 17.96%  go.SomeLeakMethod2
  512.02kB  8.77%   100%   512.02kB  8.77%  go.OtherLeakMethod2
         0     0%   100%  1048.02kB 17.96%  go.NLeakMethod
         0     0%   100%  3764.07kB 64.49%  go.NLeakMethod2

Même si l'allocateur était aligné sur tcmallocc, seule la fonction go qui était l'entrée à parcourir était affichée, et le résultat était le même que le pprof d'origine. Malheureusement, l'hypothèse selon laquelle "ils seront générés par pprof en alignant les allocateurs" est rejetée.

Mise en garde

Il est également écrit dans le référentiel original de gperftools, mais il semble que gperftools ne fonctionne pas bien sur Linux-64bit, alors faites attention là aussi. La réponse était environ 1,5 fois plus lente que d'habitude, même si c'était censé être une bibliothèque pour accélérer l'allocation de mémoire. .. Même si cela réussit, il semble que l'installation coûtera un peu cher.

Pourquoi était-ce mauvais?

Le premier était ce que je pensais de la lecture du code de malloc.go. Ces derniers peuvent également être apparus en examinant les processus et les threads.

Dans tous les cas, il y a eu des progrès dans l'amélioration du profilage de cgo avec une seule possibilité.

Ce que j'ai pensé d'essayer de m'améliorer

Des connaissances appropriées sont nécessaires pour améliorer les zones de basse couche

Grâce à la réponse de fuite de mémoire de ma première expérience, il est devenu clair que non seulement les spécifications linguistiques de go, mais aussi des connaissances en informatique sont largement nécessaires pour faire face à de telles situations. Cette fois, en particulier pour les ordinateurs, j'ai pu saisir le processeur et la mémoire, et pour les langues, j'ai pu saisir les spécifications de langue pour les utiliser, mais en même temps, j'ai apprécié l'interface pratique créée par mes prédécesseurs. J'ai également compris que je l'utilisais sans réfléchir profondément. Cela me fait prendre conscience qu'il y a encore beaucoup de choses que je ne sais pas, et je me sens serré.

Besoin d'échouer dans le bon sens

Dans la culture de développement de freee, il y a un élément "** Echouons et attaquons **". Je reconnais cela comme «un échec est une source de croissance pour moi, et je prends des mesures agressives pour éviter le même échec la prochaine fois».

Pour moi, c'est exactement ce que je pense être impliqué dans cet incident de fuite de mémoire.

  1. Que puis-je faire pour éviter que des fuites de mémoire ne se produisent à l'avenir?
  2. Si vous pouvez créer une situation dans laquelle le profilage de la mémoire peut être effectué plus facilement, vous devriez être en mesure de l'exécuter de manière proactive et de l'empêcher.
  3. Essayons

Si vous avez le sens du défi, vous vous sentirez plus motivé et vous le ferez. Nous n'oublierons pas cette façon de penser et renforcerons nos forces pour que nous puissions sûrement présenter des mesures d'amélioration.

Résumé

  1. ~~ Aligner les allocateurs Go et C ~~
  2. Créez une bibliothèque qui profilera C à partir de Go avec la même interface que net / http / pprof
  3. Faites de votre mieux avec valgrind (maintenez le statu quo)

J'ai enquêté sur 1 cette fois, mais malheureusement je n'ai pas obtenu de bons résultats. Cependant, c'était une excellente occasion de lire le code source et de le déplacer, même si je ne connaissais pas Go et C. Le reste est un plan 2 (1), mais je vais passer un peu de temps à lire le code source de Go et travailler sur 2. Je souhaite mettre à jour le rapport de suivi ultérieurement.

C'est la fin, freee Co., Ltd. est à la recherche de stagiaire ingénieur à long terme, ingénieur 17, 18 diplômés qui souhaite offrir une valeur sérieuse aux utilisateurs utilisant de nouveaux langages et technologies tels que Go. Je suis. Il y a aussi un système de déjeuner où vous pouvez venir au bureau et déjeuner. Si vous êtes intéressé par les étudiants et les travailleurs, n'hésitez pas à nous contacter.

demain, Trois ans et demi avant le développement. Mon plat préféré est le charbon de bois de Komiya. Voici notre PDG @ dice-sasaki @ github, qui nous rejoindra cette année encore! impatient de!

Recommended Posts

essais et erreurs pour améliorer le profilage de la mémoire cgo par les débutants
Essais et erreurs pour accélérer la génération de cartes thermiques
Essai et erreur pour accélérer les captures d'écran Android