De nos jours, tant de langues sont ramassées. Il est même disponible pour C ++ par des tiers. Mais C ++ a RAII et des pointeurs intelligents. Alors, quel est l'intérêt d'utiliser la récupération de place? Est-ce qu'il fait 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 récupérateurs de place? Si non, alors pourquoi n'est-ce pas?
garbage-collection
smart-pointer
Gulshan
la source
la source
Réponses:
Je suppose que vous voulez dire que les pointeurs intelligents comptés en référence sont des formes (rudimentaires) de récupération des ordures; je vais donc répondre à la question "quels sont les avantages des autres formes de récupération des ordures par rapport aux pointeurs intelligents comptés en référence" au lieu.
Précision . Le comptage de références seul fuit les cycles; ainsi, les pointeurs intelligents comptés de références perdront de la mémoire en général, à moins que d'autres techniques ne soient ajoutées aux cycles de capture. Une fois ces techniques ajoutées, le bénéfice de la simplicité du comptage de références a disparu. Notez également que les GC de comptage et de suivi de références basés sur la portée collectent des valeurs à des moments différents, parfois le comptage de références est collecté plus tôt et parfois, les GC de traçage collectés plus tôt.
Débit . Les pointeurs intelligents sont l’une des formes de récupération de place les moins efficaces, en particulier dans le contexte d’applications multithreads lorsque les décomptes de références sont modifiés de manière atomique. Il existe des techniques avancées de comptage de références conçues pour remédier à ce problème, mais le traçage des GC reste l'algorithme de choix dans les environnements de production.
Latence . Les implémentations classiques de pointeurs intelligents permettent aux destructeurs de se déclencher, ce qui entraîne des temps de pause illimités. D'autres formes de ramassage des ordures sont beaucoup plus progressives et peuvent même se faire en temps réel, par exemple le tapis de course de Baker.
la source
Étant donné que personne n’a examiné la question sous cet angle, je reformulerai votre question: pourquoi mettre quelque chose dans le langage si vous pouvez le faire dans une bibliothèque? Ignorant des détails spécifiques à l’implémentation et à la syntaxe, GC / pointeurs intelligents est fondamentalement un cas particulier de cette question. Pourquoi définir un ramasse-miettes 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 en premier:
Vous vous assurez que tout le code peut l'utiliser pour interagir. C’est, à mon avis, la principale raison pour laquelle la réutilisation et le partage de code n’a pas vraiment pris son essor avant Java / C # / Python / Ruby. Les bibliothèques ont besoin de communiquer, et leur seul langage partagé fiable est ce qu’elles contiennent (et dans une certaine mesure, leur bibliothèque standard). Si vous avez déjà essayé de réutiliser des bibliothèques en C ++, vous avez probablement subi la douleur affreuse qu'aucune sémantique de mémoire standard ne provoque. Je veux passer une struct à une lib. Est-ce que je passe une référence? Aiguille?
scoped_ptr
?smart_ptr
? Est-ce que je passe la propriété, ou pas? Y a-t-il un moyen de l'indiquer? Que faire si la lib doit allouer? Dois-je lui donner un allocateur? En ne faisant pas de la gestion de la mémoire une partie intégrante du langage, le C ++ oblige chaque paire de bibliothèques à négocier leur propre stratégie spécifique ici, et il est vraiment difficile de les mettre tous d'accord. GC en fait un non-problème complet.Vous pouvez concevoir la syntaxe autour de cela. Comme C ++ n'encapsule pas la gestion de la mémoire elle-même, il doit fournir une gamme de points d'ancrage syntaxiques pour permettre au code de niveau utilisateur d'exprimer tous les détails. Vous avez des pointeurs, des références, des
const
opérateurs de déréférencement, des opérateurs d'indirection, des adresses, etc. Si vous transférez la gestion de la mémoire dans le langage même, la syntaxe peut être conçue autour de cela. Tous ces opérateurs disparaissent et le langage devient plus propre et plus simple.Vous obtenez un retour sur investissement élevé. La valeur générée par un morceau de code donné est multipliée par le nombre de personnes l'utilisant. Cela signifie que plus vous avez d'utilisateurs, plus vous pouvez vous permettre d'acheter 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 qu’une bibliothèque uniquement utilisée par un sous-ensemble de ces utilisateurs. C'est pourquoi les langages tels que Java et C # ont des machines virtuelles de premier ordre et des éboueurs d'une qualité fantastique: le coût de leur développement est amorti par des millions d'utilisateurs.
la source
Dispose
un objet qui encapsule une bitmap, toute référence à cet objet sera une référence à un objet bitmap supprimé. Si l'objet a été supprimé prématurément alors qu'un autre code s'attend toujours à être utilisé, la classe bitmap peut garantir que l'autre code échouera de manière prévisible . En revanche, l'utilisation d'une référence à la mémoire libérée est définie par le comportement non défini.La récupération de place signifie simplement que vos objets alloués sont automatiquement libérés à un moment donné après qu’ils ne sont plus joignables.
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.
Les pointeurs intelligents font simplement référence à toute structure qui se comporte comme un pointeur ordinaire, mais qui comporte des fonctionnalités supplémentaires. Ceux - ci incluent, mais ne sont pas limités à la désallocation, mais également à la copie sur écriture, aux contrôles liés, ...
Maintenant, comme vous l'avez dit, les pointeurs intelligents peuvent être utilisés pour implémenter une forme de récupération de place.
Mais le train de pensée va de la manière suivante:
Bien sûr, vous pouvez le concevoir comme ça depuis le début. C # a été conçu pour être récupéré,
new
il ne reste donc que votre objet et il sera publié lorsque les références tomberont hors de portée. La façon dont cela est fait est à la charge du compilateur.Mais en C ++, il n'y avait pas de récupération de place prévue. Si nous allouons un pointeur
int* p = new int;
qui tombe en dehors de la portée, ilp
est lui-même retiré de la pile, mais personne ne s'occupe de la mémoire allouée.Désormais, les destructeurs déterministes sont la seule chose que vous ayez au départ . Lorsqu'un objet quitte l'étendue 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 d’opérateur, modèles, destructeurs, ... Dans cette situation de langage particulière, vous avez développé des pointeurs intelligents pour vous fournir le GC souhaité.
Mais si vous concevez une langue avec GC dès le départ, il ne s'agit que d'un détail d'implémentation. Vous dites simplement que l'objet sera nettoyé et que le compilateur le fera pour vous.
Des 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 permettant d’appeler a
.Dispose()
sur certains objets). Les ressources non référencées seront finalement récupérées par le GC, mais on ne sait pas exactement quand cela se produira.Et ceci, à son tour, peut permettre au GC de faire son travail plus efficacement. Intégré plus profondément dans le langage que les pointeurs intelligents, le GC GC .NET peut par exemple retarder des opérations de mémoire et les exécuter par blocs pour les rendre moins chères ou même déplacer la mémoire pour une efficacité accrue en fonction de la fréquence des objets sont consultés.
la source
IDisposable
etusing
. Mais cela demande un peu d’effort de la part des programmeurs, raison pour laquelle il n’est généralement utilisé que pour des ressources très rares telles que les descripteurs de connexion à une base de données.IDisposable
syntaxe plus simple en remplaçant simplement conventionnellelet ident = value
paruse ident = value
...using
n'a rien à voir avec la récupération de place, il appelle simplement une fonction lorsqu'une variable tombe en dehors de la portée, comme les destructeurs en C ++.À mon sens, il existe deux grandes différences entre la récupération de place et les pointeurs intelligents utilisés pour la gestion de la mémoire:
Le premier signifie que GC récupérera des ordures que les indicateurs intelligents ne feront pas; Si vous utilisez des pointeurs intelligents, vous devez éviter de créer ce type de déchets ou être prêt à les gérer manuellement.
Ce dernier signifie que, peu importe la façon dont les pointeurs intelligents sont intelligents, leur fonctionnement ralentira les tâches en cours dans votre programme. Le ramassage des ordures peut différer le travail et le déplacer vers d'autres threads; cela lui permet d'être globalement plus efficace (en effet, le coût d'exécution d'un GC moderne est inférieur à celui d'un système malloc / free normal, même sans la surcharge des pointeurs intelligents), et de faire le travail qu'il reste à faire sans entrer dans le manière des threads de l'application.
Notez maintenant que les pointeurs intelligents, en tant que 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 hors de la portée du garbage collection. Si vous voulez faire cela, vous aurez besoin de pointeurs intelligents.
Cependant, aux fins de la gestion de la mémoire, je ne vois aucune possibilité de remplacer des pointeurs intelligents par le nettoyage de la mémoire. Ils ne sont tout simplement pas aussi bons à ça.
la source
using
bloc dans les versions ultérieures de C #. De plus, le comportement non déterministe des GC peut être interdit dans les systèmes en temps réel (c’est pourquoi les GC ne sont pas utilisés là-bas). De plus, n'oublions pas que les GC sont si complexes à résoudre que la plupart perdent réellement de la mémoire et sont assez inefficaces (par exemple, Boehm…).Le terme collecte des ordures implique qu'il y a des ordures à ramasser. En C ++, les pointeurs intelligents se déclinent en plusieurs versions, en particulier unique_ptr. Unique_ptr est fondamentalement une propriété unique et une structure de portée. Dans un élément de code bien conçu, la plupart des ressources allouées aux segments de mémoire se trouveraient normalement derrière des pointeurs intelligents unique_ptr et la propriété de ces ressources serait toujours bien définie. Unique_ptr est pratiquement inutilisable et unique_ptr élimine la plupart des problèmes de gestion manuelle de la mémoire qui conduisaient traditionnellement les utilisateurs aux langues gérées. Maintenant que de plus en plus de cœurs fonctionnant simultanément deviennent de plus en plus courants, les principes de conception qui poussent le code à utiliser une propriété unique et bien définie à tout moment sont plus importants pour la performance.
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 nécessitent réellement, 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 à des quantités si petites qu'un programme c ++ conçu correctement devrait être considéré comme une implémentation de la collection 'litter' avec shared-ptr plutôt qu'une collecte à part entière mis en œuvre dans d'autres langues. C ++ n'a tout simplement pas beaucoup de "déchets"
Comme indiqué par d'autres, les pointeurs intelligents comptés en référence sont une forme de récupération de place, ce qui pose un problème majeur. L'exemple utilisé principalement comme inconvénient des formes de numérotation des références comptées par référence est le problème de la création de structures de données orphelines connectées à des pointeurs intelligents qui créent des clusters d'objets qui s'empêchent d'être collectés. Tandis que dans un programme conçu selon le modèle de calcul acteur, les structures de données ne permettent généralement pas à de tels clusters non récupérables d'apparaître en C ++, lorsque vous utilisez l'approche de données partagées large pour la programmation multithread, comme cela est utilisé principalement dans une grande partie de l'industrie, ces grappes d'orphelins peuvent rapidement devenir une réalité.
Donc, pour résumer, si par utilisation de pointeur partagé, vous entendez l'utilisation généralisée de unique_ptr associée au modèle d'acteur de l'approche de calcul pour la programmation multithread et l'utilisation limitée de shared_ptr, alors que les autres formes de récupération de place ne vous en achètent pas avantages supplémentaires. Si toutefois une approche tout partagé vous aboutissait avec shared_ptr partout, vous devriez envisager de changer de modèle de concurrence ou de passer à un langage géré plus adapté au partage plus large de la propriété et à l’accès simultané aux structures de données.
la source
Rust
n'est pas nécessaire de ramasser les ordures?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 des objets. Lorsque ce nombre passe à zéro, l'objet est libéré.
Le problème existe 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, vous devez manuellement pour libérer la mémoire associée à A, B et C entrez une "rupture" dans la référence circulaire (en utilisant par exemple
weak_ptr
C ++).Le ramassage des ordures (généralement) fonctionne assez différemment. La plupart des éboueurs de nos jours utilisent un test d'accessibilité . Autrement dit, il examine toutes les références de la pile et celles qui sont globalement accessibles, puis trace chaque objet auquel ces références se réfèrent, et les objets auxquels elles font référence, etc. Tout le reste est de la foutaise.
De cette manière, les références circulaires importent moins - tant que ni A, B ni C ne sont joignables , la mémoire peut être récupérée.
La "vraie" collecte des ordures offre d’autres avantages. Par exemple, l'allocation de mémoire est extrêmement économique: il suffit d'incrémenter le pointeur sur la "fin" du bloc de mémoire. La désallocation a également un coût amorti constant. Bien entendu, des langages tels que C ++ vous permettent d’implémenter la gestion de la mémoire à votre guise. Vous pouvez ainsi concevoir une stratégie d’allocation encore plus rapide.
Bien entendu, en C ++, la quantité de mémoire allouée aux segments de mémoire est généralement inférieure à un langage de référence lourd tel que C # / .NET. Mais ce n'est pas vraiment un problème de collecte de déchets contre les pointeurs intelligents.
Dans tous les cas, le problème n'est pas évident: l'un est meilleur que l'autre. Ils ont chacun des avantages et des inconvénients.
la source
C'est une question de performance . L'annulation de la mémoire nécessite beaucoup d'administration. Si la suppression de l'allocation 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 à l'instant sacré), mais les objets libérés le peuvent.
Essayez en C ++ (sans GC) d’allouer un grand nombre d’objets, d’afficher «bonjour», puis de les supprimer. 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 d'obstacles, je ne les ai jamais utilisés.
la source
Le ramassage des ordures peut être plus efficace: il regroupe en gros la surcharge de la gestion de la mémoire et le fait en une fois. En général, cela signifie que moins de ressources processeur seront utilisées pour la désallocation de mémoire, mais cela signifiera que vous aurez une grosse activité de désallocation à un moment donné. Si le CPG n'est pas correctement conçu, il peut être visible par l'utilisateur sous la forme d'une "pause" pendant que le CPG tente de désallouer de la mémoire. La plupart des GC modernes sont très efficaces pour garder cette information invisible pour l'utilisateur, sauf dans les conditions les plus défavorables.
Les pointeurs intelligents (ou tout système de comptage de références) ont l'avantage de se produire exactement au moment où vous vous attendez à consulter le code (le pointeur intelligent est hors de portée, les éléments sont supprimés). Vous obtenez de petites quantités de désaffectation ici et là. Globalement, vous pouvez utiliser plus de temps processeur lors de la désaffectation, mais comme il est réparti sur tout ce qui se passe dans votre programme, il est moins probable (affichage désaffecté d'une structure de données monstre) visible pour votre utilisateur.
Si vous faites quelque chose où la réactivité est importante, je suggérerais que les compteurs / références intelligents vous permettent de savoir exactement quand les choses se passent, afin que vous puissiez savoir tout en codant ce qui est susceptible de devenir visible pour vos utilisateurs. Dans un contexte GC, vous n’avez que le contrôle le plus éphémère sur le ramasse-miettes et vous devez simplement essayer de contourner le problème.
D'autre part, si votre objectif est d'obtenir un débit global, un système basé sur le GC peut constituer un bien meilleur choix, car il réduit 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 avez tendance à privilégier les structures de données qui ne comportent pas de cycles, ou vous faites simplement attention à la façon dont vous laissez aller ces choses. Si nécessaire, vous pouvez utiliser des objets conservateurs sachant briser les cycles des objets possédés afin d'assurer automatiquement la destruction appropriée. Dans certains domaines de la programmation, cela peut être important, mais pour la plupart des tâches quotidiennes, ce n'est pas pertinent.
la source
La limitation numéro un des pointeurs intelligents est qu’ils n’aident pas toujours les références circulaires. Par exemple, vous avez un objet A qui stocke un pointeur intelligent sur un objet B et un objet B stocke un pointeur intelligent sur un objet A. S'ils sont laissés ensemble sans réinitialiser aucun 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 triigrée dans le scénario ci-dessus car les deux objets sont inaccessibles au programme. Le ramassage des ordures fera face - il identifiera correctement que les objets ne sont pas accessibles au programme et ils seront collectés.
la source
C'est un spectre .
Si vous ne voulez pas que les performances soient serrées et que vous êtes prêt à vous lancer dans la mouture, vous vous retrouverez au montage ou à l'assemblage, avec tout le fardeau qui vous incombe de prendre les bonnes décisions et toute la liberté de le faire, mais avec , toute la liberté de tout gâcher:
"Je vais te dire quoi faire, tu le fais. Fais-moi confiance".
Le ramassage des ordures est l’autre extrémité du spectre. Vous avez très peu de contrôle, mais sa prise en charge pour vous:
"Je vais vous dire ce que je veux, vous y arrivez".
Cela présente de nombreux avantages, notamment le fait que vous n'avez pas besoin d'être aussi digne de confiance lorsqu'il s'agit de savoir exactement quand une ressource n'est plus nécessaire, mais (malgré certaines des réponses qui circulent ici), la performance n'est pas bonne, et la prévisibilité des performances. (Comme toutes les choses, si vous avez le contrôle et que vous faites quelque chose de stupide, vous pouvez avoir de pires résultats. Cependant, suggérer que savoir au moment de la compilation quelles sont les conditions pour pouvoir libérer de la mémoire, ne peut pas être utilisé comme une performance gagnante, c’est au-delà naïf).
RAII, cadrage, comptage d'arbitrage, etc. sont tous des aides pour vous permettre d'aller plus loin dans ce spectre, mais ce n'est pas tout le chemin. Toutes ces choses nécessitent encore une utilisation active. Ils vous permettent toujours d'interagir avec la gestion de la mémoire de la même manière que la récupération de place.
la source
S'il vous plaît rappelez-vous qu'à la fin, tout se résume à un CPU exécutant des instructions. À ma connaissance, tous les processeurs grand public ont des jeux d’instructions qui nécessitent que les données soient stockées à un endroit donné en mémoire et que vous disposiez de pointeurs vers ces données. C'est tout ce que vous avez au niveau de base.
Tout ce qui s’y rattache avec la récupération de place, les références aux données qui ont pu être déplacées, le compactage de tas, etc., fait le travail dans les limites données par le paradigme ci-dessus "bloc de mémoire avec un pointeur d’adresse". Même chose avec les pointeurs intelligents - vous devez ENCORE faire exécuter le code sur du matériel réel.
la source