Question

Python utilise la méthode du comptage de références pour gérer la durée de vie des objets.Ainsi un objet qui n’a plus d’utilité sera immédiatement détruit.

Mais, en Java, le GC(garbage collector) détruit les objets qui ne sont plus utilisés à un instant précis.

Pourquoi Java choisit-il cette stratégie et quel en est l'avantage ?

Est-ce mieux que l'approche Python ?

Était-ce utile?

La solution

L’utilisation du comptage de références présente des inconvénients.L’une des références circulaires les plus mentionnées :Supposons que A fasse référence à B, B fasse référence à C et C fasse référence à B.Si A devait supprimer sa référence à B, B et C auront toujours un nombre de références de 1 et ne seront pas supprimés avec le comptage de références traditionnel.CPython (le comptage de références ne fait pas partie de Python lui-même, mais de son implémentation C) capture les références circulaires avec une routine de récupération de place distincte qu'il exécute périodiquement...

Autre inconvénient :Le comptage de références peut ralentir l'exécution.Chaque fois qu'un objet est référencé et déréférencé, l'interpréteur/la machine virtuelle doit vérifier si le nombre est tombé à 0 (puis libérer si c'est le cas).Garbage Collection n’a pas besoin de faire cela.

De plus, le Garbage Collection peut être effectué dans un thread séparé (même si cela peut être un peu délicat).Sur les machines avec beaucoup de RAM et pour les processus qui n’utilisent la mémoire que lentement, vous ne voudrez peut-être pas du tout faire de GC !Le comptage de références y serait un peu un inconvénient en termes de performances...

Autres conseils

En fait, le comptage de références et les stratégies utilisées par Sun JVM sont tous différents types d'algorithmes de récupération de place.

Il existe deux grandes approches pour retrouver des objets morts :traçage et comptage de références.Lors du traçage, le GC commence à partir des "racines" - des éléments tels que les références de pile et trace tous les objets accessibles (vivants).Tout ce qui ne peut être atteint est considéré comme mort.Dans le comptage de références, chaque fois qu'une référence est modifiée, le nombre d'objets impliqués est mis à jour.Tout objet dont le compteur de références est mis à zéro est considéré comme mort.

Avec pratiquement toutes les implémentations de GC, il y a des compromis, mais le traçage est généralement bon pour les débits élevés (c'est-à-direrapide) mais avec des temps de pause plus longs (intervalles plus importants où l'interface utilisateur ou le programme peut se bloquer).Le comptage de références peut fonctionner par petits morceaux mais sera globalement plus lent.Cela peut signifier moins de gels mais des performances globales moins bonnes.

De plus, un GC à comptage de références nécessite un détecteur de cycle pour nettoyer tous les objets d'un cycle qui ne seront pas détectés par leur seul décompte de références.Perl 5 n'avait pas de détecteur de cycle dans son implémentation GC et pouvait fuir de la mémoire cyclique.

Des recherches ont également été menées pour tirer le meilleur parti des deux mondes (temps de pause faibles, débit élevé) :http://cs.anu.edu.au/~Steve.Blackburn/pubs/papers/urc-oopsla-2003.pdf

Darren Thomas donne une bonne réponse.Cependant, une grande différence entre les approches Java et Python est qu'avec le comptage de références dans le cas courant (pas de références circulaires), les objets sont nettoyés immédiatement plutôt qu'à une date ultérieure indéterminée.

Par exemple, je peux écrire du code bâclé et non portable en CPython tel que

def parse_some_attrs(fname):
    return open(fname).read().split("~~~")[2:4]

et le descripteur de fichier de ce fichier que j'ai ouvert sera nettoyé immédiatement car dès que la référence au fichier ouvert disparaît, le fichier est récupéré et le descripteur de fichier est libéré.Bien sûr, si j'exécute Jython ou IronPython ou éventuellement PyPy, le ramasse-miettes ne fonctionnera nécessairement que bien plus tard ;il est possible que je manque d'abord de descripteurs de fichiers et que mon programme plante.

Vous DEVRIEZ donc écrire du code qui ressemble à

def parse_some_attrs(fname):
    with open(fname) as f:
        return f.read().split("~~~")[2:4]

mais parfois les gens aiment compter sur le comptage de références pour toujours libérer leurs ressources, car cela peut parfois rendre votre code un peu plus court.

Je dirais que le meilleur ramasse-miettes est celui qui offre les meilleures performances, ce qui semble actuellement être les ramasse-miettes générationnels de style Java qui peuvent s'exécuter dans un thread séparé et qui ont toutes ces optimisations folles, etc.Les différences dans la façon dont vous écrivez votre code devraient être négligeables et idéalement inexistantes.

Je pense que l'article "Théorie et pratique Java :Une brève histoire de la collecte des déchets" d'IBM devrait vous aider à expliquer certaines de vos questions.

La récupération de place est plus rapide (plus efficace en termes de temps) que le comptage de références, si vous disposez de suffisamment de mémoire.Par exemple, un gc de copie parcourt les objets « vivants » et les copie dans un nouvel espace, et peut récupérer tous les objets « morts » en une seule étape en marquant une région mémoire entière.C'est très efficace, si vous avez suffisamment de mémoire.Les collections générationnelles s'appuient sur le savoir selon lequel « la plupart des objets meurent jeunes » ;souvent, seuls quelques pour cent des objets doivent être copiés.

[C'est aussi la raison pour laquelle gc peut être plus rapide que malloc/free]

Le comptage de références est beaucoup plus efficace en termes d'espace que le garbage collection, car il récupère la mémoire au moment même où elle devient inaccessible.C'est pratique lorsque vous souhaitez attacher des finaliseurs à des objets (par ex.pour fermer un fichier une fois que l'objet File devient inaccessible).Un système de comptage de références peut fonctionner même lorsque seulement quelques pour cent de la mémoire sont libres.Mais le coût de gestion lié à l'augmentation et à la décrémentation des compteurs à chaque affectation de pointeur coûte beaucoup de temps, et une sorte de garbage collection est toujours nécessaire pour récupérer les cycles.

Le compromis est donc clair :si vous devez travailler dans un environnement avec une mémoire limitée ou si vous avez besoin de finaliseurs précis, utilisez le comptage de références.Si vous disposez de suffisamment de mémoire et avez besoin de vitesse, utilisez le garbage collection.

Un gros inconvénient du traçage GC de Java est que de temps en temps, il « arrête le monde » et gèle l'application pendant une période relativement longue pour effectuer un GC complet.Si le tas est grand et l'arborescence des objets complexe, il se figera pendant quelques secondes.De plus, chaque GC complet visite encore et encore l’ensemble de l’arborescence des objets, ce qui est probablement assez inefficace.Un autre inconvénient de la façon dont Java effectue le GC est que vous devez indiquer à la machine virtuelle Java quelle taille de tas vous souhaitez (si la valeur par défaut n'est pas suffisante) ;la JVM dérive de cette valeur plusieurs seuils qui déclencheront le processus GC lorsqu'il y aura trop de déchets s'empilant dans le tas.

Je présume que c'est en fait la principale cause de la sensation de saccade d'Android (basé sur Java), même sur les téléphones portables les plus chers, en comparaison avec la fluidité d'iOS (basé sur ObjectiveC et utilisant RC).

J'aimerais voir une option jvm pour activer la gestion de la mémoire RC, et peut-être garder GC uniquement pour s'exécuter en dernier recours lorsqu'il n'y a plus de mémoire.

La dernière machine virtuelle Sun Java dispose en fait de plusieurs algorithmes GC que vous pouvez modifier.Les spécifications de la machine virtuelle Java ont intentionnellement omis de spécifier le comportement réel du GC afin de permettre différents (et plusieurs) algorithmes GC pour différentes machines virtuelles.

Par exemple, pour tous ceux qui n'aiment pas l'approche « arrêter le monde » du comportement par défaut de Sun Java VM GC, il existe des VM telles que WebSphere temps réel d'IBM qui permet à une application en temps réel de s'exécuter sur Java.

Étant donné que la spécification Java VM est accessible au public, rien (théoriquement) n'empêche quiconque d'implémenter une machine virtuelle Java qui utilise l'algorithme GC de CPython.

Le comptage de références est particulièrement difficile à réaliser efficacement dans un environnement multithread.Je ne sais pas comment vous commenceriez même à le faire sans vous lancer dans des transactions assistées par matériel ou des instructions atomiques similaires (actuellement) inhabituelles.

Le comptage de références est facile à mettre en œuvre.Les JVM ont investi beaucoup d'argent dans des implémentations concurrentes, il ne devrait donc pas être surprenant qu'elles mettent en œuvre de très bonnes solutions à des problèmes très difficiles.Cependant, il devient de plus en plus facile de cibler votre langage préféré sur la JVM.

Tard dans le jeu, mais je pense qu'une des raisons importantes du RC en python est sa simplicité.Regarde ça e-mail d'Alex Martelli, Par exemple.

(Je n'ai pas trouvé de lien en dehors du cache Google, l'e-mail date du 13 octobre 2005 sur la liste Python).

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top