Qu’est-ce que le nettoyage de la mémoire et comment affecte-t-il les performances de votre programme? –
Le garbage collection est une fonctionnalité de nombreux langages tels que C # et Java. Alors que la gestion manuelle de la mémoire comme C ++ peut être assez rapide, le garbage collection automatique améliore la qualité de vie des développeurs. Cependant, il est important de comprendre les implications sur le rendement de quitter GC pour faire votre travail.
Sommaire
Pile contre tas
Pour comprendre ce que fait un garbage collector, vous devez d’abord comprendre la différence entre la mémoire stockée sur la pile et la mémoire stockée sur le tas. Les deux sont des emplacements de mémoire spécifiques dans la mémoire attribuée à votre programme, à partir de la RAM disponible de votre ordinateur.
Le Empiler est rapide et est utilisé pour les types valeur qui ont une taille d’octet fixe. C’est la même mémoire physique que le tas bien sûr, c’est juste rapide parce que c’est une structure de données très ordonnée First-In, Last-Out. Chaque fois que vous créez une variable locale, elle la stocke sur la pile, et chaque fois que votre fonction se termine et que la variable sort de la portée, elle est automatiquement nettoyée de la pile.
C’est un processus très rapide et rend les allocations de pile pratiquement gratuites. Bien qu’il y ait une pénalité de performance, c’est aussi bon marché que possible.
Le Tas, d’autre part, est utilisé pour des objets volumineux tels que des listes et des chaînes qui sont trop volumineuses pour être stockées sur la pile, ou qui doivent être stockées longtemps après que les fonctions soient hors de portée, ce que les allocations de pile ne peuvent pas faire par conception. Chaque fois que tu fais new object
, vous faites une allocation de tas. Vous faites probablement aussi une allocation de pile, car si vous stockez une référence dans une variable locale, cette variable locale doit être créée pour pointer vers la mémoire réelle du nouvel objet.
Le tas est un peu plus lent, à la fois pour allouer de la mémoire et pour supprimer. Cela s’applique à tous les langages utilisant ce modèle, avec ou sans garbage collector.
Nettoyer vos ordures
Bien sûr, ce n’est pas aussi simple que de simplement «allouer une fois et oublier cela». Si nous ne supprimions jamais la mémoire, nous aurions un fuite de mémoire, lequel est très mauvais et va rapidement consommer la RAM de votre machine. Les allocations telles que les listes locales seront immédiatement hors de portée, mais sans nettoyage, obstrueraient le tas pour toujours. Ainsi, les programmes doivent avoir un moyen de nettoyer la mémoire qui n’est plus nécessaire.
Dans les langages de gestion manuelle de la mémoire comme C ++, la mémoire est gérée manuellement. Vous devez libérer manuellement de la mémoire et supprimer les objets que vous n’utilisez plus, à l’aide d’une référence ou d’un pointeur vers l’emplacement mémoire de cet objet. Bien que cela puisse être extrêmement rapide, ce n’est pas amusant à coder et peut entraîner des bogues de mémoire et des exploits. C’est l’une des principales raisons pour lesquelles le C ++ est considéré comme un langage de programmation «difficile» à apprendre et à coder.
L’alternative à la gestion manuelle consiste à laisser la machine le faire automatiquement pour vous. C’est ce que l’on appelle le garbage collection.
Un garbage collector s’exécute sur un thread d’arrière-plan et analyse périodiquement le tas et la pile de votre application, et recherche les objets qui n’ont plus de références. Cela signifie que l’objet est sans valeur et peut être supprimé en toute sécurité sans affecter le programme.
Par exemple, prenez le pseudocode suivant, qui crée et «supprime» un objet
object refToObject = new object(); refToObject = null;
Puisque refToObject
ne fait plus référence au new object()
qui a été créé, le garbage collector verra que le nouvel objet est suspendu sans y faire référence de n’importe où, et le supprimera chaque fois qu’il ramassera des déchets la prochaine fois.
Le garbage collector est également assez intelligent et est capable de résoudre les dépendances circulaires. Par exemple, si vous avez deux objets qui se référencent l’un à l’autre, mais que rien d’autre ne les connaît, c’est des déchets. Dans la plupart des cas, si un objet n’a pas de chaîne de référence partant de la racine du programme et menant à l’objet, c’est des déchets.
Le garbage collection peut être déclenché à tout moment, généralement:
- Lorsque le système manque de mémoire.
- Le pourcentage de mémoire sur le tas dépasse un certain seuil. Ce seuil est ajusté automatiquement et correspond essentiellement à chaque fois que le CPG voit que votre programme doit être nettoyé.
- Lorsqu’il est déclenché manuellement, comme avec
GC.Collect()
.
Impacts sur les performances
Bien sûr, le ramassage des ordures n’est pas du tout gratuit. Si c’était le cas, chaque langue l’utiliserait. GC est lent, principalement parce qu’il doit interrompre l’exécution du programme pour collecter les déchets.
Pensez-y comme ceci: votre processeur ne peut fonctionner que sur une seule chose à la fois. Avec C ++, il travaille toujours sur votre code, y compris les bits qui effacent la mémoire. Avec un GC, votre programme ne supprime pas la mémoire et s’exécute jusqu’à ce qu’il fasse des déchets. Ensuite, il est mis en pause et le processeur bascule pour travailler sur le garbage collection. S’il le fait souvent, cela peut ralentir les performances de l’application.
Habituellement, c’est assez rapide, généralement moins de quelques millisecondes au maximum. Pour .NET, cela dépend du type de mémoire en cours de nettoyage, car il garde la trace de la mémoire dans différentes «générations»:
- Génération 0, la plus jeune génération qui contient des objets éphémères comme des variables temporaires.
- Génération 1, qui agit comme un tampon entre les objets à court et à long terme. Si un objet survit à une tentative de nettoyage de la mémoire, il sera «promu» à une génération supérieure.
- Génération 2, le dernier, qui suit les objets à long terme.
Le GC vérifiera les objets dans Gen0, puis Gen1, puis Gen2. Comme ils ne contiennent que des objets temporaires ou nouvellement créés, le nettoyage de Gen0 et Gen1 est généralement assez rapide, mais Gen2 contient beaucoup de mémoire. Faire un «garbage collection complet» peut être beaucoup plus lent que les garbage collection éphémères.
Comment accélérer les performances?
Alors, que pouvez-vous faire pour éviter cela? Eh bien, à la fin de la journée, vos ordures doivent être ramassées. La seule chose réelle que vous pouvez faire est de réduire la quantité de déchets que votre programme jette.
L’un des principaux moyens de réduire les déchets consiste à utiliser Regroupement d’objets. Le principe de base derrière cela est qu’il est souvent plus rapide de réinitialiser les objets par défaut que d’en créer un nouveau et de jeter l’ancien.
Par exemple, le code suivant effectue une itération de 10 000 fois et crée une nouvelle liste à chaque fois pour en faire quelque chose. Cependant, c’est horrible sur le GC, donc une meilleure méthode consiste à faire une grande liste et à l’effacer une fois que vous en avez terminé et que vous en voulez une nouvelle.
En pratique, cela se fait généralement avec un «Object Pool» générique, qui gère une liste d’objets qu’il peut «louer» à votre programme. Lorsque votre code est terminé, il libère l’objet dans le pool et le réinitialise, prêt à être utilisé à nouveau sur demande.