Maison > Article > développement back-end > Explication détaillée du mécanisme de collecte des ordures de Golang GC
Ce qui suit est une explication détaillée du mécanisme de collecte des ordures de Golang GC de la colonne tutoriel Golang J'espère que cela sera utile aux amis dans le besoin !
En utilisation réelle de go Au cours du processus d'apprentissage du langage, j'ai rencontré des phénomènes d'utilisation de la mémoire apparemment étranges, j'ai donc décidé de mener des recherches sur le modèle de garbage collection du langage Go. Cet article résume les résultats de l’étude.
Autrefois, la gestion de la mémoire était un problème majeur pour les programmeurs développant des applications. Dans les langages de programmation traditionnels au niveau du système (principalement C/C++), les programmeurs doivent gérer soigneusement la mémoire et contrôler l'application et la libération de la mémoire. Si vous n'y faites pas attention, des fuites de mémoire peuvent survenir. Ce type de problème est difficile à trouver et à localiser, et a toujours été un cauchemar pour les développeurs. Comment résoudre ce problème de maux de tête ? Dans le passé, deux méthodes étaient généralement utilisées :
Afin de résoudre ce problème, presque tous les nouveaux langagesdéveloppés ultérieurement (java, python, php, etc.) ont introduit la gestion automatique de la mémoire au niveau du langage – c'est-à-dire le langage les utilisateurs doivent uniquement prêter attention à Vous n'avez pas à vous soucier de la libération de mémoire lors de la demande de mémoire. La libération de mémoire est automatiquement gérée par la machine virtuelle ou le runtime. Ce recyclage automatique des ressources mémoire qui ne sont plus utilisées est appelé garbage collection.
Il s'agit de l'algorithme de collecte des ordures le plus simple, qui est le même que le pointeur intelligent mentionné précédemment. Conservez un nombre de références pour chaque objet. Lorsque l'objet référençant l'objet est détruit ou mis à jour, le nombre de références de l'objet référencé est automatiquement diminué de un. Lorsque l'objet référencé est créé ou attribué à un autre objet, le nombre de références est automatiquement augmenté. par un. Lorsque le compteur de références atteint 0, l'objet est immédiatement recyclé.
L'avantage de cette méthode est qu'elle est simple à mettre en œuvre et que la mémoire est recyclée dans les meilleurs délais. Cet algorithme est largement utilisé dans les systèmes avec une mémoire restreinte et des performances en temps réel élevées, tels que le framework iOS Cocoa, PHP, Python, etc. L'algorithme simple de comptage de références présente également des inconvénients évidents :
Cette méthode est divisée en deux étapes. Mark commence à partir de la variable racine et parcourt tous les objets référencés. Les objets sont accessibles via le parcours de l'application. Tous les objets sont. marquée comme « référencée » une fois le marquage terminé, l'opération d'effacement est effectuée et la mémoire non marquée est recyclée (le recyclage peut être accompagné d'opérations de défragmentation). Cette méthode résout les défauts du comptage de références, mais elle présente également un problème évident : chaque fois que le garbage collection est démarré, toute l'exécution normale actuelle du code sera suspendue et le recyclage réduira considérablement la réactivité du système ! Bien entendu, de nombreuses variantes de l’algorithme mark&sweep (telles que la méthode de marquage tricolore) ont vu le jour pour optimiser ce problème.
Après de nombreuses observations réelles, nous savons que dans les langages de programmation orientés objet, le cycle de vie de la plupart des objets est très court. L'idée de base de la collection générationnelle est de diviser le tas en deux ou plusieurs espaces appelés générations. Les objets nouvellement créés sont stockés dans ce qu'on appelle la jeune génération (d'une manière générale, la taille de la jeune génération sera beaucoup plus petite que celle de l'ancienne génération). Avec l'exécution répétée du garbage collection, les objets avec des cycles de vie plus longs seront promus (promotion). ) à l'ancienne génération. Par conséquent, deux méthodes différentes de garbage collection, le garbage collection de nouvelle génération et le garbage collection d’ancienne génération, ont vu le jour, qui sont utilisées pour effectuer le garbage collection sur les objets dans leurs espaces respectifs. La vitesse du garbage collection dans la nouvelle génération est très rapide, plusieurs ordres de grandeur plus rapide que celle de l'ancienne génération. Même si la fréquence du garbage collection dans la nouvelle génération est plus élevée, l'efficacité d'exécution est toujours meilleure que celle de l'ancienne génération. génération. En effet, le cycle de vie de la plupart des objets est très court et il n'est pas du tout nécessaire de les promouvoir auprès de l'ancienne génération.
Le garbage collection du langage go utilise généralement l'algorithme classique de marquage et de balayage.
Le problème le plus courant et le plus difficile rencontré par l'équipe lors de la pratique du langage go était le problème de mémoire (principalement gc). Voici les problèmes et expériences rencontrés en résumé. , tout le monde est invité à communiquer et à discuter.
Ce problème a été découvert lorsque nous avons effectué un test de stress sur le service en arrière-plan. Nous avons simulé un grand nombre de demandes d'utilisateurs pour accéder au service en arrière-plan. À ce stade, chaque module de service peut observer une augmentation significative de l'utilisation de la mémoire. Cependant, lorsque le test de résistance a été arrêté, l’utilisation de la mémoire n’a pas diminué de manière significative. Il a fallu beaucoup de temps pour localiser le problème, en utilisant diverses méthodes telles que gprof, mais aucune cause n'a toujours été trouvée. Finalement, j'ai découvert que c'était normal à ce moment-là... Il y a deux raisons principales,
Tout d'abord, le garbage collection de Go a un seuil de déclenchement, qui augmentera progressivement à mesure que chaque utilisation de la mémoire augmente (par exemple, si le seuil initial est de 10 Mo, la prochaine fois, il sera de 20 Mo, et la prochaine fois, il deviendra de 40 Mo. ..), si gc go n'est pas déclenché pendant une longue période, il se déclenchera activement une fois (2min). Une fois que l'utilisation de la mémoire augmente pendant les heures de pointe, à moins que vous ne continuiez à demander de la mémoire, il est presque impossible de déclencher gc en fonction du seuil. Au lieu de cela, vous devez attendre jusqu'à 2 minutes pour que le gc actif démarre avant de déclencher gc.
La deuxième raison est que lorsque le langage Go renvoie la mémoire au système, il indique simplement au système que la mémoire n'est plus nécessaire et peut être recyclée en même temps, le système d'exploitation adoptera un système d'exploitation. stratégie de « retard », qui ne recycle pas immédiatement, mais attendra que la mémoire système soit saturée avant de commencer le recyclage, de sorte que lorsque le programme redemande de la mémoire, il puisse obtenir une vitesse d'allocation extrêmement rapide.
Pour les programmes back-end qui obligent les utilisateurs à répondre aux événements, arrêter le monde à temps partiel pendant Golang gc est un cauchemar. Selon l'introduction ci-dessus, les performances gc de la version 1.5 de go seront considérablement améliorées après avoir terminé les améliorations ci-dessus. Cependant, tous les langages de garbage collection seront inévitablement confrontés à une dégradation des performances pendant gc. À cet égard, nous devrions essayer d'éviter. créer fréquemment des tas temporaires (tels que &abc{}, new, make, etc.) pour réduire le temps d'analyse lors de la récupération de place. Pour les objets temporaires qui doivent être utilisés fréquemment, envisagez de les réutiliser directement via le cache du tableau. utilisez la méthode cgo pour gérer la mémoire eux-mêmes et contourner le garbage collection. , cette méthode n'est pas recommandée à moins qu'elle ne soit absolument nécessaire (elle peut facilement causer des problèmes imprévisibles). Bien sûr, elle peut toujours être envisagée si elle est absolument nécessaire. de cette méthode est encore très évidente~
L'un de nos services doit gérer de nombreuses demandes de connexion longues Lors de la mise en œuvre, une coroutine de lecture et d'écriture est ouverte pour chaque longue connexion. demande, et une boucle for sans fin est utilisée pour traiter en continu les données d'envoi et de réception. Lorsque la connexion est fermée par l'extrémité distante, si ces deux coroutines ne sont pas traitées, elles continueront à fonctionner et les canaux occupés ne seront pas libérés... Vous devez être très prudent ici, et vous devez les supprimer après ne pas les utiliser. les coroutines. Fermez le canal dépendant et déterminez si le canal est fermé dans la coroutine pour assurer sa sortie.
30 AVRIL 2016 20:02 | COMMENTAIRES
Cette partie présente principalement quelques connaissances introductives de Golang gc, car le contenu de gc implique Il y en a d'autres, et je vais les trier petit à petit.
La référence principale est la suivante :
http://morsmachine.dk/machine-gc
est 14 Il a été écrit en 2008. On estime que le mécanisme gc à cette époque était relativement simple. La nouvelle version de golang devrait avoir des changements plus importants par rapport à gc
Il y a aussi la partie pertinente sur golang gc dans le langage go. notes de lecture
Le terme « Memory Leak » me semble familier, mais en fait je n'ai jamais vu sa signification précise. La
Fuite de mémoire s'explique du point de vue du système d'exploitation. La métaphore frappante est que « l'espace de stockage (espace de mémoire virtuelle) que le système d'exploitation peut fournir à tous les processus est utilisé. par un certain processus "Drain", la raison en est que lorsque le programme est en cours d'exécution, il ouvrira dynamiquement et en continu de l'espace de stockage, et ces espaces de stockage ne seront pas libérés à temps une fois l'opération terminée. Après qu'une application ait alloué un certain segment de mémoire, en raison d'erreurs de conception, le programme peut perdre le contrôle du segment de mémoire, entraînant un gaspillage d'espace mémoire.
Si un programme demande un morceau de mémoire dans l'espace mémoire, puis après la fin du programme, l'espace mémoire n'est pas libéré et le programme correspondant ne dispose pas d'un bon mécanisme gc pour exécuter l'espace Le recyclage demandé par le programme entraînera des fuites de mémoire.
Du point de vue de l'utilisateur, la fuite de mémoire en elle-même ne causera aucun dommage, car elle n'affecte pas les fonctions de l'utilisateur, mais si une "fuite de mémoire" se produit
Pour C et C++, pour les langages sans Garbage Collection, nous nous concentrons principalement sur deux types de fuites de mémoire :
Il existe de nombreux problèmes liés aux fuites de mémoire, qui ne seront pas abordés ici.
Pour les avantages et les inconvénients spécifiques, veuillez vous référer à ceci. Voici juste une introduction générale.
Par conséquent, deux méthodes différentes de collecte des ordures, la collecte des ordures de nouvelle génération et la collecte des ordures de l'ancienne génération, ont vu le jour (classer d'abord, puis prescrire le bon médicament), qui sont utilisées pour effectuer la collecte des ordures sur objets dans leurs espaces respectifs. La vitesse du garbage collection dans la nouvelle génération est très rapide, plusieurs ordres de grandeur plus rapide que celle de l'ancienne génération. Même si la fréquence du garbage collection dans la nouvelle génération est plus élevée, l'efficacité d'exécution est toujours meilleure que celle de l'ancienne génération. génération. En effet, le cycle de vie de la plupart des objets est très court et il n'est pas du tout nécessaire de les promouvoir auprès de l'ancienne génération.
Le gc dans Golang est essentiellement l'idée de l'effacement des marques :
Dans le tas de mémoire (en raison de la gestion parfois de la mémoire Le tas La structure de données est utilisée lors de la création de pages, elle est donc appelée mémoire tas) qui stocke une série d'objets, qui peuvent être liés à d'autres objets (références entre ces objets, un garbage collector de traçage arrêtera le programme à un moment donné). qui est en cours d'exécution, puis il analysera l'ensemble d'objets déjà connu que le runtime connaît déjà. Il s'agit généralement de variables globales et de divers objets qui existent dans la pile. gc marquera ces objets, marquera le statut de ces objets comme accessibles, trouvera toutes les références d'objets dans d'autres endroits qui peuvent être atteints à partir des objets actuels et marquera ces objets comme objets accessibles. Cette étape est appelée phase de marquage, c'est-à-dire. c'est-à-dire phase de marquage. L'objectif principal de cette étape est d'obtenir les informations d'état de ces objets.
Une fois tous ces objets analysés, gc obtiendra tous les objets inaccessibles (objets avec un statut inaccessible) et les recyclera. Cette étape est appelée phase de balayage, qui estPhase de nettoyage.
gc collecte uniquement les objets qui ne sont pas marqués comme accessibles. Si gc ne reconnaît pas de référence, un objet encore utilisé peut éventuellement être recyclé, provoquant une erreur d'exécution du programme.
Vous pouvez voir les trois étapes principales : numérisation, recyclage et nettoyage.
Je pense que comparé à d'autres langages, le modèle de collecte des ordures dans Golang est relativement simple.
gc peut être considéré comme ayant été introduit pour résoudre le problème du recyclage de la mémoire. Lors de l'utilisation de langages nouvellement développés (java, python, php, etc.), les utilisateurs n'ont pas besoin de se soucier de la libération des objets de mémoire, mais doivent uniquement se soucier de l'application des objets en effectuant des opérations associées au moment de l'exécution ou dans la machine virtuelle. , Pour obtenir l'effet de gestion automatique de l'espace mémoire, ce comportement de recyclage automatique des ressources mémoire qui ne sont plus utilisées est appelé garbage collection.
Selon la déclaration précédente, le fait que gc puisse identifier correctement une référence est la base pour que gc fonctionne normalement. Par conséquent, la première question est de savoir comment gc doit identifier une référence ?
Le plus gros problème : il est difficile d'identifier les références, et le code machine est difficile de savoir ce qui compte comme référence. Si une référence est manquée par erreur, la mémoire qui n'était pas prête à être libérée le sera désormais par erreur, la stratégie consiste donc à pécher par excès de plus que de moins.
Une stratégie consiste à traiter tous les espaces mémoire comme des références possibles (valeurs de pointeur). C’est ce qu’on appelle un ramasse-miettes conservateur. C'est ainsi que fonctionne le garbage collector Boehm en C. C'est-à-dire que les variables ordinaires en mémoire sont traitées comme des pointeurs et tentent de couvrir tous les pointeurs. Si par hasard il y a d'autres objets dans l'espace pointés par la valeur de la variable ordinaire, alors cet objet ne sera pas recyclé. L'implémentation du langage go est pleinement consciente des informations de type de l'objet et ne traversera que l'objet pointé par le pointeur lors du marquage, évitant ainsi le gaspillage de mémoire tas dans l'implémentation C (résolution d'environ 10 à 30 %). Marquage tricolore
2015/8 1.5 Présentation du marquage tricolore
Concernant l'introduction du nettoyage simultané, reportez-vous à ici. Dans la version 1.3, le runtime go séparait les opérations de marquage et de balayage Comme auparavant, l'exécution de toutes les tâches est d'abord suspendue et le marquage démarre (la partie marquage nécessite toujours l'original). programme arrêté), la tâche suspendue sera redémarrée immédiatement après la fin du marquage et la tâche de balayage sera exécutée en parallèle avec d'autres tâches de la même manière que les tâches de coroutine ordinaires. S'il fonctionne sur un processeur multicœur, go essaiera d'exécuter la tâche gc sur un cœur séparé sans affecter l'exécution du code métier. L'équipe go elle-même affirme qu'elle réduit le temps de pause de 50 à 70 %.
L'algorithme de base est le nettoyage + recyclage mentionné précédemment. Le cœur de l'optimisation de Golang gc est d'essayer de rendre le temps STW (Stop The World) de plus en plus court.
Comment mesurer GC
. GODEBUG=gctrace=1 ./myserver
Selon l'analyse précédente, nous pouvons également savoir que gc en golang utilise la méthode de marquage clair, donc le temps total de gc est :
Tgc = Tseq + Tmark + Tsweep
Par exemple, le programme actuel utilise 4 Mo de mémoire (on parle ici de mémoire tas
), ce qui signifie que la mémoire actuellement accessible du programme est de 4 Mo lorsque la mémoire est occupée par. le programme atteint l'accessibilité* Lorsque (1+GOGC/100)=8M, gc sera déclenché et les opérations gc associées commenceront.La manière de définir les paramètres GOGC doit être déterminée en fonction du scénario réel en production, par exemple en augmentant les paramètres GOGC pour réduire la fréquence de GC.
ConseilsSi vous souhaitez avoir des informations approfondies, c'est essentiel lors de l'utilisation de gdb. Cet article a compilé quelques conseils d'introduction à l'utilisation de gdb.Réduire l'allocation d'objets
La soi-disant réduction de l'allocation d'objets signifie en fait réutiliser les objets autant que possible. Par exemple, les deux définitions de fonction suivantes :La première fonction n'a pas de paramètres formels et renvoie un []byte à chaque fois qu'elle est appelée. La deuxième fonction a un paramètre formel de buf à chaque fois qu'elle est appelée. ]objet de type octet, renvoie ensuite le nombre d'octets lus.
La première fonction allouera un espace à chaque fois qu'elle sera appelée, ce qui entraînera une pression supplémentaire sur gc. La deuxième fonction réutilisera la déclaration formelle des paramètres à chaque fois qu'elle sera appelée.Un discours banal sur la conversion de chaîne et de []octet
La conversion entre chaîne et []octet mettra la pression sur gc Grâce à gdb, vous pouvez d'abord comparer les structures de données des deux :Lorsque les deux sont convertis, la structure de données sous-jacente sera copiée, donc l'efficacité du gc deviendra inférieure. En termes de stratégie de solution, une solution consiste à toujours utiliser []byte, en particulier en termes de transmission de données, []byte contient également de nombreuses opérations efficaces couramment utilisées dans les chaînes. L'autre consiste à utiliser des opérations de niveau inférieur pour convertir directement afin d'éviter la copie. Vous pouvez vous référer à la première partie de l'optimisation des performances dans WeChat « Yuhen Academy », qui utilise principalement unsafe.Pointer pour la conversion directe.
Concernant l'utilisation de unsafe, je pense que je peux compiler un article séparé. Tout d'abord, répertoriez les informations pertinentes ici http://studygolang.com/articles/685 Intuitivement, vous pouvez comprendre unsafe.Pointer Into void. * en C++, en golang, c'est l'équivalent d'un pont de conversion de différents types de pointeurs.
Le type sous-jacent de uintptr est int, qui peut contenir la valeur de l'adresse pointée par le pointeur. Il peut être converti vers et depuis unsafe.Pointer. La principale différence est que uintptr peut participer aux opérations de pointeur, tandis qu'unsafe.Pointer ne peut effectuer que la conversion de pointeur et ne peut pas effectuer d'opérations de pointeur. Si vous souhaitez utiliser Golang pour l'arithmétique des pointeurs, vous pouvez vous y référer. Lors de l'exécution d'opérations de pointeur spécifiques, il doit d'abord être converti en type uintptr avant de pouvoir effectuer d'autres calculs, tels que le décalage, etc.
Utilisez + avec parcimonie pour connecter une chaîne Puisque l'utilisation de + pour connecter des chaînes générera de nouveaux objets et réduira l'efficacité de gc, le meilleur moyen est d'utiliser la fonction append.
Mais il y a un autre inconvénient. Par exemple, référez-vous au code suivant :
Après avoir utilisé l'opération append, l'espace du tableau augmente de 1024 à 1312, donc si la longueur du tableau tableau peut être connu à l'avance, il est préférable de le faire. Heureusement, la planification de l'espace est effectuée lorsque l'espace est initialement alloué, ce qui augmentera certains coûts de gestion du code, tout en réduisant la pression sur gc et en améliorant l'efficacité du code.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!