it-swarm.dev

Pourquoi Garbage Collection s'il y a des pointeurs intelligents

De nos jours, tant de langues sont récupérées. Il est même disponible pour C++ par des tiers. Mais C++ a RAII et des pointeurs intelligents. Quel est donc l'intérêt d'utiliser la récupération de place? Fait-il quelque chose de plus?

Et dans d'autres langages comme C #, si toutes les références sont traitées comme des pointeurs intelligents (en gardant RAII de côté), par spécification et par implémentation, y aura-t-il encore besoin de ramasse-miettes? Si non, pourquoi n'en est-il pas ainsi?

69
Gulshan

Alors, quel est l'intérêt d'utiliser la récupération de place?

Je suppose que vous voulez dire des pointeurs intelligents comptés par référence et je noterai qu'il s'agit d'une forme (rudimentaire) de collecte des ordures, donc je répondrai à la question "quels sont les avantages d'autres formes de collecte des ordures par rapport aux pointeurs intelligents comptés par référence" au lieu.

  • Précision. Le comptage de référence seul fait fuir les cycles, de sorte que les pointeurs intelligents comptés par référence fuiront la mémoire en général, sauf si d'autres techniques sont ajoutées aux cycles de capture. Une fois ces techniques ajoutées, l'avantage de la simplicité du comptage des références a disparu. Notez également que les GC de comptage et de suivi de référence basés sur la portée collectent des valeurs à des moments différents, parfois le comptage de référence recueille plus tôt et parfois les GC de traçage collectent plus tôt.

  • Débit. Les pointeurs intelligents sont l'une des formes les moins efficaces de récupération de place, en particulier dans le contexte des applications multithreads lorsque les comptages de références sont générés par atomisation. Il existe des techniques avancées de comptage des références conçues pour remédier à cela, mais les GC de traçage restent l'algorithme de choix dans les environnements de production.

  • Latence. Les implémentations de pointeurs intelligents typiques permettent aux destructeurs d'avalanche, ce qui entraîne des temps de pause illimités. D'autres formes de collecte des ordures sont beaucoup plus incrémentielles et peuvent même être en temps réel, par exemple Tapis roulant de boulanger.

71
Jon Harrop

Puisque personne ne l'a regardé sous cet angle, je reformulerai votre question: pourquoi mettre quelque chose dans le langage si vous pouvez le faire dans une bibliothèque? Ignorer l'implémentation spécifique et les détails syntaxiques, GC/smart pointeurs est essentiellement un cas particulier de cette question. Pourquoi définir un garbage collector dans le langage lui-même si vous pouvez l'implémenter dans une bibliothèque?

Il y a quelques réponses à cette question. Le plus important d'abord:

  1. Vous vous assurez que tout le code peut l'utiliser pour interopérer. C'est, je pense, la grande raison pour laquelle la réutilisation et le code du code le partage n'a pas vraiment décollé avant Java/C #/Python/Ruby. Les bibliothèques ont besoin de communiquer, et le seul langage partagé fiable qu'elles ont est ce qu'il y a dans la spécification linguistique elle-même (et, dans une certaine mesure, sa bibliothèque standard). Si vous avez déjà essayé de réutiliser des bibliothèques en C++, vous avez probablement connu la douleur horrible qu'aucune sémantique de mémoire standard ne provoque. Je veux passer une structure à une lib. Dois-je transmettre une référence? Aiguille? scoped_ptr? smart_ptr? Suis-je en train de transmettre la propriété, ou non? Y a-t-il un moyen de l'indiquer? Et si la bibliothèque doit allouer? Dois-je lui attribuer un allocateur? En ne faisant pas de la gestion de la mémoire une partie du langage, C++ oblige chaque paire de bibliothèques à négocier leur propre stratégie spécifique ici, et il est vraiment difficile de les faire tous s'entendre. GC en fait un non-problème complet.

  2. Vous pouvez concevoir la syntaxe autour d'elle. Parce que C++ n'encapsule pas la gestion de la mémoire elle-même, il doit fournir une gamme de crochets syntaxiques pour permettre au code de niveau utilisateur d'exprimer tous les détails. Vous avez des pointeurs, des références, const, des opérateurs de déréférencement, des opérateurs d'indirection, l'adresse de, etc. Si vous intégrez la gestion de la mémoire dans le langage lui-même, la syntaxe peut être conçue autour de cela. Tous ces opérateurs disparaissent et la langue devient plus propre et plus simple.

  3. Vous obtenez un retour sur investissement élevé. La valeur qu'un morceau de code donné génère est multipliée par le nombre de personnes l'utilisant. Cela signifie que plus vous avez d'utilisateurs, plus vous pouvez vous permettre de dépenser pour un logiciel. Lorsque vous déplacez une fonctionnalité dans la langue, tous les utilisateurs de la langue l'utilisent. Cela signifie que vous pouvez y consacrer plus d'efforts que vous ne le pourriez à une bibliothèque utilisée uniquement par un sous-ensemble de ces utilisateurs. C'est pourquoi des langages comme Java et C # ont des machines virtuelles de premier ordre et des ramasse-miettes d'une qualité fantastique: le coût de leur développement est amorti sur des millions d'utilisateurs.

66
munificent

Garbage collection signifie simplement que vos objets alloués sont automatiquement libérés à un moment donné après qu'ils ne soient plus accessibles.

Plus précisément, ils sont libérés lorsqu'ils deviennent inaccessibles pour le programme, car les objets référencés de manière circulaire ne seraient jamais libérés autrement.

Pointeurs intelligents fait simplement référence à toute structure qui se comporte comme un pointeur ordinaire mais possède des fonctionnalités supplémentaires. Ces comprennent mais ne se limitent pas à la désallocation, mais aussi à la copie sur écriture, aux chèques liés, ...

Maintenant, comme vous l'avez dit, des pointeurs intelligents peuvent être utilisés pour implémenter une forme de ramasse-miettes.

Mais le train de pensée va comme suit:

  1. La collecte des ordures est une chose cool à avoir, car c'est pratique et je dois m'occuper de moins de choses
  2. Par conséquent: je veux la collecte des ordures dans ma langue
  3. Maintenant, comment obtenir GC dans ma langue?

Bien sûr, vous pouvez le concevoir comme ça dès le départ. C # a été conçu pour être récupéré, donc juste new votre objet et il sera publié lorsque les références seront hors de portée. La façon dont cela est fait dépend du compilateur.

Mais en C++, aucune récupération de place n'était prévue. Si nous allouons un pointeur int* p = new int; Et qu'il tombe hors de portée, p lui-même est supprimé de la pile, mais personne ne s'occupe de la mémoire allouée.

Maintenant, la seule chose que vous avez depuis le début est des destructeurs déterministes . Lorsqu'un objet quitte la portée dans laquelle il a été créé, son destructeur est appelé. En combinaison avec des modèles et une surcharge d'opérateur, vous pouvez concevoir un objet wrapper qui se comporte comme un pointeur, mais utilise la fonctionnalité de destructeur pour nettoyer les ressources qui lui sont attachées (RAII). Vous appelez celui-ci un pointeur intelligent .

Tout cela est très spécifique à C++: surcharge des opérateurs, modèles, destructeurs, ... Dans cette situation de langage particulière, vous avez développé des pointeurs intelligents pour vous fournir le GC que vous voulez.

Mais si vous concevez un langage avec GC depuis le début, ce n'est qu'un détail d'implémentation. Vous dites simplement que l'objet sera nettoyé et que le compilateur le fera pour vous.

Les pointeurs intelligents comme en C++ ne seraient probablement même pas possibles dans des langages comme C #, qui n'ont aucune destruction déterministe (C # contourne cela en fournissant du sucre syntaxique pour appeler une .Dispose() sur certains objets). Les ressources non référencées seront finalement récupérées par le GC, mais il n'est pas défini quand exactement cela se produira.

Et cela, à son tour, peut permettre au GC de faire son travail plus efficacement. Étant intégré plus profondément dans le langage que les pointeurs intelligents, qui sont placés dessus, le GC .NET peut par exemple retardez les opérations de mémoire et effectuez-les par blocs pour les rendre moins chères ou même déplacer la mémoire pour augmenter l'efficacité en fonction de la fréquence d'accès aux objets.

36
Dario

Il y a deux grandes différences, à mon avis, entre la récupération de place et les pointeurs intelligents utilisés pour la gestion de la mémoire:

  1. Les pointeurs intelligents ne peuvent pas collecter les déchets cycliques; collecte des ordures
  2. Les pointeurs intelligents effectuent tout le travail au moment du référencement, du déréférencement et de la désallocation, sur le thread d'application; la collecte des ordures n'a pas besoin

Le premier signifie que le GC collectera les déchets que les pointeurs intelligents ne le feront pas; si vous utilisez des pointeurs intelligents, vous devez éviter de créer ce type de déchets ou être prêt à les traiter manuellement.

Ce dernier signifie que peu importe la façon dont les pointeurs intelligents sont intelligents, leur fonctionnement ralentira les threads de travail dans votre programme. La récupération de place peut différer le travail et le déplacer vers d'autres threads; qui lui permet d'être plus efficace dans l'ensemble (en effet, le coût d'exécution d'un GC moderne est inférieur à un système malloc/gratuit normal, même sans la surcharge supplémentaire de pointeurs intelligents), et de faire le travail qu'il lui reste à faire sans entrer dans le manière des threads d'application.

Maintenant, notez que les pointeurs intelligents, étant des constructions programmatiques, peuvent être utilisés pour faire toutes sortes d'autres choses intéressantes - voir la réponse de Dario - qui sont complètement en dehors de la portée de la récupération de place. Si vous voulez faire cela, vous aurez besoin de pointeurs intelligents.

Cependant, à des fins de gestion de la mémoire, je ne vois aucune perspective de pointeurs intelligents remplaçant le ramasse-miettes. Ils ne sont tout simplement pas aussi bons dans ce domaine.

4
Tom Anderson

Le terme garbage collection implique qu'il y a des déchets à collecter. En C++, les pointeurs intelligents sont disponibles en plusieurs versions, surtout le unique_ptr. L'unique_ptr est fondamentalement une propriété unique et une construction de portée. Dans un morceau de code bien conçu, la plupart des éléments alloués par tas résideraient normalement derrière des pointeurs intelligents unique_ptr et la propriété de ces ressources sera bien définie à tout moment. Il n'y a pratiquement pas de surcharge dans unique_ptr et unique_ptr supprime la plupart des problèmes de gestion manuelle de la mémoire qui poussaient traditionnellement les gens vers les langages gérés. Maintenant que de plus en plus de cœurs s'exécutent simultanément, les principes de conception qui poussent le code à utiliser une propriété unique et bien définie à tout moment deviennent plus importants pour les performances. L'utilisation du modèle de calcul des acteurs permet la construction de programmes avec un minimum d'état partagé entre les threads, et la propriété unique joue un rôle majeur pour que les systèmes hautes performances utilisent efficacement de nombreux cœurs sans les frais généraux du partage entre les threads data et les exigences implicites du mutex.

Même dans un programme bien conçu, en particulier dans des environnements multithreads, tout ne peut pas être exprimé sans structures de données partagées, et pour les structures de données qui en ont vraiment besoin, les threads doivent communiquer. RAII en c ++ fonctionne assez bien pour les problèmes de durée de vie dans une configuration à un seul thread, dans une configuration à plusieurs threads, la durée de vie des objets peut ne pas être complètement définie de manière hiérarchique. Pour ces situations, l'utilisation de shared_ptr offre une grande partie de la solution. Vous créez la propriété partagée d'une ressource et cela en C++ est le seul endroit où nous voyons des ordures, mais à de si petites quantités qu'un programme c ++ conçu correctement devrait être davantage considéré pour implémenter la collecte de "litière" avec des ptr partagés que la collecte de déchets à part entière comme implémenté dans d'autres langues. C++ n'a tout simplement pas autant de "déchets" à collecter.

Comme indiqué par d'autres, les pointeurs intelligents comptés par référence sont une forme de collecte des ordures, et pour cela, un problème majeur. L'exemple qui est principalement utilisé comme inconvénient des formes de collecte des ordures comptées par référence est le problème avec la création de structures de données orphelines connectées les unes aux autres par des pointeurs intelligents qui créent des clusters d'objets qui s'empêchent d'être collectés. Alors que dans un programme conçu selon le modèle d'acteur du calcul, les structures de données ne permettent généralement pas à de tels clusters non collectables de se produire en C++, lorsque vous utilisez l'approche de données partagées large pour la programmation multi-thread, comme cela est utilisé principalement dans une grande partie de l'industrie, ces grappes orphelines peuvent rapidement devenir une réalité.

Donc, pour résumer le tout, si par utilisation de pointeur partagé, vous entendez la large utilisation de unique_ptr combinée avec le modèle d'acteur de l'approche de calcul pour la programmation multithread et l'utilisation limitée de shared_ptr, que d'autres formes de collecte des ordures ne vous en achètent pas avantages supplémentaires. Si toutefois une approche tout partagé vous obligeait à vous retrouver avec shared_ptr partout, alors vous devriez envisager de changer de modèle de concurrence ou de passer à un langage géré plus axé sur le partage plus large de la propriété et l'accès simultané aux structures de données.

4
user1703394

La plupart des pointeurs intelligents sont implémentés à l'aide du comptage de références. Autrement dit, chaque pointeur intelligent qui fait référence à un objet incrémente le nombre de références d'objets. Lorsque ce nombre atteint zéro, l'objet est libéré.

Le problème se pose si vous avez des références circulaires. Autrement dit, A a une référence à B, B a une référence à C et C a une référence à A. Si vous utilisez des pointeurs intelligents, afin de libérer la mémoire associée à A, B & C, vous devez manuellement y entrer une "rupture" de la référence circulaire (par exemple en utilisant weak_ptr en C++).

La récupération de place (généralement) fonctionne de manière très différente. La plupart des récupérateurs utilisent de nos jours un test d'accessibilité. Autrement dit, il examine toutes les références de la pile et celles qui sont accessibles globalement, puis trace tous les objets auxquels ces références se réfèrent, et les objets ils font référence, etc. Tout le reste est une ordure .

De cette façon, les références circulaires n'ont plus d'importance - tant que ni A, B et C ne sont accessibles, la mémoire peut être récupérée.

Il existe d'autres avantages à la "vraie" collecte des ordures. Par exemple, l'allocation de mémoire est extrêmement bon marché: il suffit d'incrémenter le pointeur jusqu'à la "fin" du bloc de mémoire. La désallocation a également un coût amorti constant. Mais bien sûr, des langages comme C++ vous permettent d'implémenter la gestion de la mémoire à peu près comme vous le souhaitez, vous pouvez donc trouver une stratégie d'allocation encore plus rapide.

Bien sûr, en C++, la quantité de mémoire allouée par segment de mémoire est généralement inférieure à un langage de référence comme C # /. NET. Mais ce n'est pas vraiment un problème de collecte de déchets par rapport aux pointeurs intelligents.

Dans tous les cas, le problème n'est pas coupé l'un et l'autre est meilleur que l'autre. Ils ont chacun des avantages et des inconvénients.

2
Dean Harding

Il s'agit de performances . La répartition de la mémoire nécessite beaucoup d'administration. Si la désallocation s'exécute en arrière-plan, les performances du processus de premier plan augmentent. Malheureusement, l'allocation de mémoire ne peut pas être paresseuse (les objets alloués seront utilisés au prochain instant sacré), mais la libération d'objets le peut.

Essayez en C++ (sans GC) d'allouer un gros tas d'objets, imprimez "bonjour", puis supprimez-les. Vous serez surpris du temps nécessaire pour libérer des objets.

De plus, GNU libc fournit des outils plus efficaces pour désallouer de la mémoire, voir obstacks . Je dois remarquer que je n'ai aucune expérience des obstacks, je ne les ai jamais utilisés.

2
ern0

Le ramassage des ordures peut être plus efficace - il "concentre" essentiellement les frais généraux de la gestion de la mémoire et fait tout en même temps. En général, cela entraînera moins de dépenses globales du processeur pour la désallocation de la mémoire, mais cela signifie que vous aurez une grande explosion d'activité de désallocation à un moment donné. Si le GC n'est pas correctement conçu, cela peut devenir visible pour l'utilisateur comme une "pause" pendant que le GC essaie de désallouer de la mémoire. La plupart des GC modernes sont très bons pour garder cela invisible pour l'utilisateur sauf dans les conditions les plus défavorables.

Les pointeurs intelligents (ou tout schéma de comptage de références) ont l'avantage qu'ils se produisent exactement lorsque vous vous attendez à regarder le code (le pointeur intelligent sort du cadre, la chose est supprimée). Vous obtenez de petites rafales de désaffectation ici et là. Dans l'ensemble, vous pouvez utiliser plus de temps processeur lors de la désallocation, mais comme il est réparti sur toutes les choses qui se produisent dans votre programme, il est moins probable (sauf la désallocation d'une structure de données monstre) de devenir visible pour votre utilisateur.

Si vous faites quelque chose où la réactivité est importante, je suggérerais que les pointeurs intelligents/comptage de références vous permettent de savoir exactement quand les choses se produisent, afin que vous puissiez savoir tout en codant ce qui risque de devenir visible pour vos utilisateurs. Dans un environnement GC, vous n'avez que le contrôle le plus éphémère sur le ramasse-miettes et vous n'avez qu'à essayer de contourner la chose.

D'un autre côté, si le débit global est votre objectif, un système basé sur GC peut être un bien meilleur choix, car il minimise les ressources nécessaires à la gestion de la mémoire.

Cycles: Je ne considère pas que le problème des cycles soit important. Dans un système où vous avez des pointeurs intelligents, vous tenez vers des structures de données qui n'ont pas de cycles, ou vous faites simplement attention à la façon dont vous abandonnez ces choses. Si nécessaire, des objets de gardien qui savent comment briser les cycles dans les objets possédés peuvent être utilisés afin d'assurer automatiquement une destruction appropriée. Dans certains domaines de programmation, cela peut être important, mais pour la plupart des tâches quotidiennes, cela n'a pas d'importance.

2
Michael Kohne

La limitation numéro un des pointeurs intelligents est qu'ils n'aident pas toujours contre les références circulaires. Par exemple, vous avez l'objet A stockant un pointeur intelligent vers l'objet B et l'objet B stocke un pointeur intelligent vers l'objet A. S'ils sont laissés ensemble sans réinitialiser l'un des pointeurs, ils ne seront jamais désalloués.

Cela se produit car un pointeur intelligent doit effectuer une action spécifique qui ne sera pas triée dans le scénario ci-dessus car les deux objets sont inaccessibles au programme. La collecte des ordures fonctionnera - elle identifiera correctement que les objets ne sont pas accessibles au programme et ils seront collectés.

1
sharptooth

C'est un spectre.

Si vous n'avez pas de limites strictes sur la performance et êtes prêt à mettre le Grind à contribution, vous vous retrouverez à l'Assemblée ou c, avec tout le fardeau sur vous de prendre les bonnes décisions et toute la liberté de le faire, mais avec lui , toute la liberté de tout gâcher:

"Je vais vous dire quoi faire, vous le faites. Faites-moi confiance".

La collecte des ordures est l'autre extrémité du spectre. Vous avez très peu de contrôle, mais c'est pris en charge pour vous:

"Je vais vous dire ce que je veux, vous le réalisez".

Cela présente de nombreux avantages, surtout que vous n'avez pas besoin d'être aussi fiable lorsqu'il s'agit de savoir exactement quand une ressource n'est plus nécessaire, mais (malgré certaines des réponses flottant ici) n'est pas bon pour les performances, et la prévisibilité des performances. (Comme toutes choses, si vous avez le contrôle et que vous faites quelque chose de stupide, vous pouvez obtenir de moins bons résultats. Cependant, suggérer que, au moment de la compilation, quelles sont les conditions pour pouvoir libérer de la mémoire, ne peut pas être utilisé comme un gain de performances est au-delà de la naïveté).

RAII, cadrage, comptage de références, etc sont tous des aides pour vous permettre de vous déplacer plus loin sur ce spectre mais ce n'est pas tout à fait là-bas. Toutes ces choses nécessitent toujours une utilisation active. Ils vous permettent et vous obligent toujours à interagir avec la gestion de la mémoire d'une manière que la collecte des ordures ne fait pas.

1
drjpizzle

N'oubliez pas qu'en fin de compte, tout se résume à un CPU exécutant des instructions. À ma connaissance, tous les processeurs de consommation ont des jeux d'instructions qui vous obligent à avoir des données stockées dans un endroit donné en mémoire et vous avez des pointeurs vers ces données. C'est tout ce que vous avez au niveau de base.

Tout cela en plus de la récupération de place, des références aux données qui ont pu être déplacées, du compactage de tas, etc. Même chose avec les pointeurs intelligents - vous devez TOUJOURS faire exécuter le code sur le matériel réel.

0
user1249