J'aime beaucoup la gestion de la mémoire en fonction de la portée (SBMM), ou RAII , comme il est plus communément appelé (source de confusion) par la communauté C ++. Autant que je sache, à l'exception de C ++ (et C), il n'y a pas d'autre langage courant utilisé aujourd'hui qui fait de SBMM / RAII leur principal mécanisme de gestion de la mémoire. Ils préfèrent plutôt utiliser le garbage collection (GC).
Je trouve cela assez déroutant, car
- SBMM rend les programmes plus déterministes (vous pouvez dire exactement quand un objet est détruit);
- dans les langues qui utilisent GC, vous devez souvent gérer manuellement les ressources (voir la section Fermeture de fichiers en Java, par exemple), ce qui va en partie à l’encontre de l’objet de GC et est également sujet à des erreurs;
- La mémoire de tas peut aussi (très élégamment, imo) être liée à la portée (voir
std::shared_ptr
en C ++).
Pourquoi SBMM n'est-il pas plus utilisé? Quels sont ses inconvénients?
finalize()
méthode d' un objet sera appelée avant la récupération de place. En réalité, cela crée la même classe de problèmes que la récupération de place est supposée résoudre.Réponses:
Commençons par postuler que la mémoire est de loin (des dizaines, des centaines voire des milliers de fois) plus commune que toutes les autres ressources combinées. Chaque variable, objet, membre d'objet a besoin de mémoire allouée et libérée ultérieurement. Pour chaque fichier que vous ouvrez, vous créez des dizaines à des millions d'objets pour stocker les données extraites du fichier. Chaque flux TCP est associé à un nombre illimité de chaînes d'octets temporaires créées pour être écrites dans le flux. Sommes-nous sur la même page ici? Génial.
Pour que RAII fonctionne (même si vous avez des pointeurs intelligents prêts à l'emploi pour chaque cas d'utilisation sous le soleil), vous devez en assurer la propriété . Vous devez analyser qui doit posséder tel ou tel objet, qui ne doit pas, et lorsque la propriété doit être transférée de A à B. Bien sûr, vous pouvez utiliser la propriété partagée pour tout , mais vous émuleriez un GC par des pointeurs intelligents. À ce stade, il devient beaucoup plus facile et rapide d’intégrer le CPG dans le langage.
Le ramassage des ordures vous libère de cette préoccupation pour la ressource de loin la plus utilisée, la mémoire. Bien sûr, vous devez toujours prendre la même décision pour les autres ressources, mais celles-ci sont beaucoup moins courantes (voir ci-dessus) et la propriété complexe (par exemple, partagée) l'est également. La charge mentale est réduite de manière significative.
Maintenant, vous nommez quelques inconvénients à rendre toutes les valeurs ramassées. Cependant, il est extrêmement difficile d’ intégrer à la fois des types de CPG et de valeur sécurisés avec RAII dans une seule langue. Il est donc peut-être préférable de migrer ces compromis par d’autres moyens.
La perte de déterminisme n’est pas si grave dans la pratique, car elle n’affecte que la durée de vie des objets déterministes . Comme décrit dans le paragraphe suivant, la plupart des ressources (hormis la mémoire, qui est abondante et peut être recyclée assez lentement) ne sont pas tenues de faire l'objet d'une durée de vie dans ces langues. Il existe quelques autres cas d'utilisation, mais ils sont rares dans mon expérience.
Votre deuxième point, la gestion manuelle des ressources, est à présent traité via une instruction qui effectue un nettoyage basé sur la portée, mais ne couple pas ce nettoyage à la durée de vie de l'objet (donc n'interagissant pas avec le GC et la sécurité de la mémoire). Ceci est
using
en C #,with
en Python,try
avec les ressources dans les versions récentes de Java.la source
using
les déclarations ne sont possibles que localement. Il est impossible de nettoyer les ressources contenues dans les variables membres de cette façon.using
est une blague par rapport à RAII, juste pour que vous sachiez.RAII découle également de la gestion automatique de la mémoire de comptage de références, telle que celle utilisée par Perl. Bien que le comptage de références soit facile à mettre en œuvre, déterministe et assez performant, il ne peut pas traiter les références circulaires (elles provoquent des fuites), raison pour laquelle il n’est pas couramment utilisé.
Les langages récupérés ne peuvent pas utiliser directement RAII, mais offrent souvent une syntaxe avec un effet équivalent. En Java, nous avons l'instruction try-with-ressource
qui appelle automatiquement
.close()
la ressource en sortie de bloc. C # a l'IDisposable
interface, ce qui permet.Dispose()
d'être appelé en laissant uneusing (...) { ... }
déclaration. Python a l'with
énoncé suivant:qui fonctionne de manière similaire. Dans un tour intéressant à ce sujet, la méthode d'ouverture de fichier de Ruby reçoit un rappel. Une fois le rappel exécuté, le fichier est fermé.
Je pense que Node.js utilise la même stratégie.
la source
with-open-filehandle
fonctions qui ouvrent le fichier, le cèdent à une fonction et, au retour de la fonction, referment le fichier.À mon avis, l’avantage le plus convaincant du ramassage des ordures est qu’il permet la composition . La correction de la gestion de la mémoire est une propriété locale dans un environnement de récupération. Vous pouvez examiner chaque partie séparément et déterminer si elle peut perdre de la mémoire. Combinez un nombre quelconque de parties correctes en mémoire et elles restent correctes.
Lorsque vous comptez sur le comptage de références, vous perdez cette propriété. Si votre application peut perdre de la mémoire, cela devient une propriété globale de toute l'application avec un comptage de références. Chaque nouvelle interaction entre les pièces a la possibilité d’utiliser la mauvaise propriété et de casser la gestion de la mémoire.
Cela a un effet très visible sur la conception des programmes dans les différentes langues. Les programmes dans les langages GC ont tendance à être un peu plus riches en objets avec beaucoup d'interactions, alors que dans les langages sans GC, on préfère les parties structurées avec des interactions strictement contrôlées et limitées entre elles.
la source
Les fermetures sont une caractéristique essentielle de pratiquement toutes les langues modernes. Ils sont très faciles à implémenter avec GC et très difficiles (bien que pas impossibles) à utiliser correctement RAII, car l’une de leurs principales caractéristiques est qu’ils vous permettent d’abstraire sur toute la durée de vie de vos variables!
C ++ ne les a obtenus que 40 ans après les autres, et il a fallu beaucoup de travail acharné de la part de personnes intelligentes pour les obtenir. En revanche, de nombreux langages de script conçus et implémentés par des personnes n'ayant aucune connaissance en conception et implémentation de langages de programmation en sont dotés.
la source
[&]
syntaxe. Tout programmeur C ++ associe déjà le&
signe à des références et connaît les références obsolètes.Pour la plupart des programmeurs, le système d'exploitation n'est pas déterministe, leur allocateur de mémoire n'est pas déterministe et la plupart des programmes qu'ils écrivent sont concurrents et, par conséquent, intrinsèquement non déterministes. Ajouter la contrainte selon laquelle un destructeur est appelé exactement à la fin de la portée plutôt que légèrement avant ou légèrement après n'est pas un avantage pratique significatif pour la grande majorité des programmeurs.
Voir
using
en C # etuse
en F #.En d'autres termes, vous pouvez prendre le tas qui est une solution à usage général et le changer pour ne fonctionner que dans un cas spécifique qui limite sérieusement. C'est vrai, bien sûr, mais inutile.
SBMM limite ce que vous pouvez faire:
SBMM crée le problème du funarg ascendant avec les fermetures lexicales de première classe, raison pour laquelle les fermetures sont populaires et faciles à utiliser dans des langages tels que C # mais rares et délicates en C ++. Notez qu'il existe une tendance générale vers l'utilisation de constructions fonctionnelles dans la programmation.
SBMM nécessite des destructeurs et ils empêchent les appels finaux en ajoutant du travail supplémentaire à effectuer avant le retour d'une fonction. Les appels de queue sont utiles pour les machines à états extensibles et sont fournis par des éléments tels que .NET.
Certaines structures de données et certains algorithmes sont notoirement difficiles à mettre en œuvre avec SBMM. Fondamentalement, partout où les cycles se produisent naturellement. Plus particulièrement, les algorithmes de graphes. Vous finissez par écrire votre propre GC.
La programmation simultanée est plus difficile parce que le flux de contrôle et, par conséquent, la durée de vie des objets sont intrinsèquement non déterministes ici. Les solutions pratiques dans les systèmes de transmission de messages ont tendance à être une copie en profondeur des messages et l'utilisation de durées de vie excessivement longues.
SBMM conserve les objets en vie jusqu'à la fin de leur portée dans le code source, ce qui est souvent plus long que nécessaire et peut être beaucoup plus long que nécessaire. Cela augmente la quantité de déchets flottants (objets inaccessibles en attente de recyclage). En revanche, le suivi de la récupération de place a tendance à libérer des objets peu de temps après la disparition de la dernière référence, ce qui peut être beaucoup plus tôt. Voir Mythes de gestion de la mémoire: rapidité .
SBMM est tellement contraignant que les programmeurs ont besoin d’une issue de secours pour les situations dans lesquelles des durées de vie ne peuvent pas être imbriquées. En C ++,
shared_ptr
offre une issue de secours, mais elle peut être environ 10 fois plus lente que le traçage de la récupération de place . Donc, utiliser SBMM au lieu de GC mettrait la plupart des gens mal pris la plupart du temps. Cela ne veut cependant pas dire que c'est inutile. SBMM est toujours utile dans le contexte des systèmes et de la programmation intégrée où les ressources sont limitées.FWIW, vous voudrez peut-être consulter Forth et Ada et vous renseigner sur le travail de Nicolas Wirth.
la source
shared_ptr
n’est que rare en C ++, car il est très lent. Deuxièmement, il s’agit d’une comparaison de pommes et d’oranges (comme l’a déjà mentionné l’article cité précédemment), car elleshared_ptr
est beaucoup plus lente que la production en GC. Troisièmement, les GC ne sont pas omniprésents et sont évités dans des logiciels tels que LMax et le moteur FIX de Rapid Addition.En regardant un indice de popularité comme TIOBE (ce qui est discutable, bien sûr, mais je suppose que pour votre genre de question, vous pouvez l'utiliser), vous voyez d'abord qu'environ 50% des 20 meilleurs sont des "langages de script" ou des "dialectes SQL ", où la" facilité d'utilisation "et les moyens d'abstraction ont beaucoup plus d'importance que les comportements déterministes. Parmi les langues "compilées" restantes, il existe environ 50% des langues avec SBMM et environ 50% sans. Ainsi, lorsque vous retirez les langages de script de votre calcul, je dirais que votre hypothèse est tout simplement fausse. Parmi les langages compilés, ceux avec SBMM sont aussi populaires que ceux sans.
la source
Un avantage majeur d'un système de GC, que personne n'a encore mentionné, est qu'une référence dans un système de GC conserve son identité tant qu'elle existe . Si on appelle
IDisposable.Dispose
(.NET) ouAutoCloseable.Close
(Java) sur un objet alors que des copies de la référence existent, ces copies continueront à faire référence au même objet. L'objet ne sera plus utile pour rien, mais les tentatives d'utilisation auront un comportement prévisible contrôlé par l'objet lui-même. En revanche, en C ++, si le code appelledelete
un objet et essaie de l'utiliser ultérieurement, tout l'état du système devient totalement indéfini.Une autre chose importante à noter est que la gestion de la mémoire basée sur la portée fonctionne très bien pour les objets avec une propriété clairement définie. Cela fonctionne beaucoup moins bien, et parfois même carrément mal, avec des objets qui n’ont pas de propriété définie. En général, les objets mutables doivent avoir des propriétaires, alors que les objets immuables n'en ont pas besoin, mais il y a une erreur: il est très courant que le code utilise une instance de types mutables pour contenir des données immuables, en s'assurant qu'aucune référence ne sera exposée à code qui pourrait muter l'instance. Dans un tel scénario, les instances de la classe mutable peuvent être partagées entre plusieurs objets immuables et ne doivent donc pas être clairement définies.
la source
Tout d’abord, il est très important de comprendre que l’assimilation de RAII à SBMM. ou même à SBRM. Une des qualités les plus essentielles (et les moins connues ou les moins appréciées) de RAII est le fait qu’elle «fait de la ressource» une propriété qui n’EST PAS transitive en composition.
Le billet de blog suivant aborde cet aspect important de la RAII et le compare à un arrangement de ressources dans des langages GCed qui utilisent un GC non déterministe.
http://minorfs.wordpress.com/2011/04/29/why-garbage-collection-is-anti-productive/
Il est important de noter que bien que RAII soit principalement utilisé en C ++, Python (enfin la version non basée sur VM) possède des destructeurs et des GC déterministes qui permettent d’utiliser RAII avec GC. Le meilleur des deux mondes s'il en était.
la source
File.ReadLines file |> Seq.length
les descripteurs de fichier, j'écris du code comme à l' endroit où les abstractions gèrent la fermeture pour moi. Serrures et threads que j'ai remplacés par .NETTask
et F #MailboxProcessor
. Cet ensemble "nous avons explosé la quantité de gestion manuelle des ressources" est un non-sens total.