2008-08-22 7 views
46

Python utilise la méthode de comptage de références pour gérer la durée de vie des objets. Donc 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 moment précis.Pourquoi les méthodes de récupération de place Java et Python sont-elles différentes?

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

Est-ce mieux que l'approche Python?

Répondre

43

L'utilisation du comptage de références présente certains inconvénients. L'une des plus citées est celle des références circulaires: Supposons que les références B, B référencent les références C et C. Si A doit abandonner sa référence à B, B et C auront toujours un nombre de références égal à 1 et ne seront pas supprimées. avec comptage de référence traditionnel. CPython (le comptage de référence ne fait pas partie de Python lui-même, mais fait partie de l'implémentation C) récupère des références circulaires avec une routine séparée de garbage collection qu'il exécute périodiquement ...

Un autre inconvénient: le comptage de références peut rendre l'exécution plus lente. Chaque fois qu'un objet est référencé et déréférencé, l'interpréteur/machine virtuelle doit vérifier si le compte est descendu à 0 (et ensuite libérer si c'est le cas). Garbage Collection n'a pas besoin de faire cela.

En outre, Garbage Collection peut être effectué dans un thread séparé (même si cela peut être un peu difficile). Sur les machines avec beaucoup de RAM et pour les processus qui utilisent la mémoire seulement lentement, vous ne voudrez peut-être pas faire GC du tout! Le comptage de référence serait un peu un inconvénient là en termes de performance ...

+16

Une autre différence à noter est que GC désireux par comptage de références utilise toujours la mémoire « minimale » (sauf dans le cas circulaire de dépendance), alors que l'approche paresseuse de Java peut entraîner la machine virtuelle Java d'utiliser temporairement beaucoup plus de mémoire que réellement nécessaire, jusqu'à ce qu'un La course GC le remet en ligne. L'approche de Java donne la vitesse au détriment de la mémoire, et a l'avantage quand la mémoire est abondante. Quand c'est rare, l'approche de Python fonctionnera mieux. –

+1

Le comptage de référence est plus lent que le marquage/balayage GC pour plusieurs autres raisons: 1. les écritures de mémoire pour mettre à jour les comptes de référence sont coûteuses et entraînent des problèmes de simultanéité puisqu'elles nécessitent une synchronisation. 2. Les compteurs de référence eux-mêmes utilisent de la mémoire supplémentaire, ce qui augmente la taille de l'objet et, par conséquent, augmente la pression du cache. – mikera

13

Darren Thomas donne une bonne réponse. Cependant, une grande différence entre les approches Java et Python est qu'avec le comptage des références dans le cas commun (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 bâclée, code non portable dans CPython tels que

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

et le descripteur de fichier pour ce fichier j'ai ouvert sera nettoyé immédiatement parce que dès que la référence à l'ouverture le fichier 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 peut-être PyPy, alors le garbage collector ne fonctionnera pas forcément beaucoup plus tard; peut-être que je vais d'abord manquer de descripteurs de fichiers et mon programme va planter.

Donc, vous devriez être en train d'écrire un 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érence pour toujours libérer leurs ressources, car il peut parfois rendre votre code un peu plus court. Je dirais que le meilleur garbage collector est celui avec les meilleures performances, qui semblent actuellement être les garbage collecteurs générationnels Java qui peuvent fonctionner dans un thread séparé et a 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.

+4

Bonne réponse. Pour moi, un point majeur de votre réponse est l'absence d'un 'close()' explicite dans le second exemple. Tellement moins de code pour écrire en python. Ceci est expliqué à http://docs.python.org/howto/doanddont.html (recherche de "avec open") –

2

La dernière version de Sun Java VM comporte en réalité plusieurs algorithmes GC que vous pouvez modifier.Les spécifications de la machine virtuelle Java ont intentionnellement omis de spécifier le comportement GC réel pour autoriser des algorithmes GC différents (et multiples) pour différentes machines virtuelles. Par exemple, pour toutes les personnes qui n'aiment pas l'approche «stop the world» du comportement par défaut de Sun Java VM GC, il existe des machines virtuelles telles que IBM's WebSphere Real Time qui permettent à une application en temps réel de s'exécuter sur Java.

Étant donné que la spécification Java VM est disponible publiquement, rien (en théorie) n'empêche quiconque d'implémenter une machine virtuelle Java utilisant l'algorithme GC de CPython.

+0

Il interdit probablement le comptage de références simple (en supposant que vous ne pouvez pas ajouter des choses à la référence de comptage ne fuit pas sous aucun circonstance). Je ne suis pas sûr de la façon dont python traite cela, bien que je pense qu'il a au moins une sorte de vérification des cycles. –

2

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

Le comptage de référence est facile à mettre en œuvre. Les JVM ont eu beaucoup d'argent investi dans des implémentations concurrentes, il ne devrait donc pas être surprenant qu'ils 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 langue préférée à la JVM.

+0

Heureusement cela ne semble pas vraiment être un problème en Python, avec le support de threading lamentable, donc c'est finalement un positif pour le GIL ... (Sauf si vous êtes sur Jython/IPython/etc ofc) – Basic

5

La récupération de place est plus rapide (plus rapide) que le comptage de référence si vous disposez de suffisamment de mémoire. Par exemple, un copieur gc traverse les objets "live" et les copie dans un nouvel espace, et peut récupérer tous les objets "morts" en une étape en marquant une région de mémoire entière. C'est très efficace, si vous avez assez de mémoire. Les collections générationnelles utilisent la connaissance que «la plupart des objets meurent jeunes»; souvent seulement quelques pour cent des objets doivent être copiés.

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

comptage de référence est beaucoup plus efficace que l'enlèvement des ordures, car il récupère la mémoire le moment même où il obtient injoignable. Cela est pratique lorsque vous souhaitez attacher des finaliseurs à des objets (par exemple, pour fermer un fichier une fois que l'objet Fichier est inaccessible). Un système de comptage de référence peut fonctionner même si seulement quelques pourcents de la mémoire sont libres. Mais le coût de gestion d'avoir à incrémenter et décrémenter les compteurs lors de chaque attribution de pointeur coûte beaucoup de temps, et un certain type de garbage collection est toujours nécessaire pour récupérer des cycles. Donc, le compromis est clair: si vous devez travailler dans un environnement à mémoire limitée, ou si vous avez besoin de finaliseurs précis, utilisez le comptage des références. Si vous avez assez de mémoire et avez besoin de la vitesse, utilisez la récupération de place.

26

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

Il existe deux approches générales pour la traçabilité des objets morts: le suivi et le comptage des références. En traçant le GC commence à partir des «racines» - des choses comme des 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érence chaque fois qu'une référence est modifiée, les objets impliqués ont leur compte mis à jour. Tout objet dont le compteur de référence est mis à zéro est considéré comme mort. Avec fondamentalement toutes les implémentations de GC, il y a des compromis, mais le traçage est généralement bon pour un fonctionnement élevé (c'est-à-dire rapide) mais a des temps de pause plus longs (des espaces plus grands où l'interface utilisateur ou le programme peuvent geler). Le comptage de référence peut fonctionner en plus petits morceaux mais sera globalement plus lent. Cela peut signifier moins de gels mais de moins bonnes performances dans l'ensemble.

En outre, un compteur de comptage de référence 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 comptage de référence. Perl 5 n'avait pas de détecteur de cycle dans sa mise en œuvre GC et pouvait perdre de la mémoire cyclique.

La recherche a également été fait pour obtenir le meilleur des deux mondes (temps faible pause, débit élevé): http://cs.anu.edu.au/~Steve.Blackburn/pubs/papers/urc-oopsla-2003.pdf

1

Tard dans le jeu, mais je pense que l'une des raisons importantes pour RC en python est sa simplicité. Voir ce email by Alex Martelli, par exemple.

(Je n'ai pas pu trouver de lien en dehors de google cache, l'adresse e-mail date du 13 octobre 2005 sur la liste python).

+1

Je pense que cela est le mauvais lien. – mtasic85

3

Un gros inconvénient du GC traçage de Java est que de temps en temps, il « arrêter le monde » et geler l'application pendant un temps relativement long pour faire un plein GC. Si le tas est grand et l'arbre de l'objet complexe, il gèlera pendant quelques secondes. De plus, chaque GC complet visite l'arbre objet entier encore et encore, ce qui est probablement très inefficace. Un autre inconvénient de la façon dont Java GC est que vous devez dire à la jvm quelle taille de tas vous voulez (si la valeur par défaut n'est pas assez bonne); la JVM dérive de cette valeur plusieurs seuils qui vont déclencher le processus du GC quand il y a trop de pile dans le tas. Je présume que c'est en fait la principale cause de la sensation saccadée d'Android (basée sur Java), même sur les téléphones portables les plus chers, en comparaison avec la finesse d'iOS (basé sur ObjectiveC, et en utilisant RC).

J'aimerais voir une option jvm pour activer la gestion de la mémoire RC, et peut-être garder GC que de courir en dernier recours quand il n'y a plus de mémoire disponible.

+0

Java GC (Garbage Collection) bloque l'exécution lorsque GC s'exécute. Donc, il ne convient pas pour les applications qui doivent être réactives à tout moment où la mise en pause n'est pas acceptable. RC (comptage de référence) est utilisé dans Objective C et Swift qui est utilisé dans les appareils Apple. Donc iOS se sent plus réactif. En bref, les langages RC comme python, ObjectiveC, Swift etc. ont des performances prévisibles et ne font pas de pause pour faire GC. Java prend également trop de mémoire à l'avance pour son tas, privant ainsi d'autres applications de RAM utilisable où python utilise seulement autant de mémoire que nécessaire pour fonctionner. – codefire

+0

http://patshaughnessy.net/2013/10/24/visualizing-garbage-collection-in-ruby-and-python – codefire

+0

@codefire certains traçage gc mettra pas en cause l'arrêt du monde. Azul Zing JVM l'a implémenté. Voici l'algorithme, http://www.azulsystems.com/sites/default/files/images/c4_paper_acm.pdf –

Questions connexes