2008-08-22 11 views
46

Python usa el método de conteo de referencias para manejar el tiempo de vida del objeto. Entonces un objeto que no tiene más uso será destruido inmediatamente.¿Por qué los métodos de recolección de basura de Java y Python son diferentes?

Pero, en Java, el GC (recolector de basura) destruye objetos que ya no se utilizan en un momento específico.

¿Por qué Java elige esta estrategia y cuál es el beneficio de esto?

¿Es esto mejor que el enfoque de Python?

Respuesta

43

Existen inconvenientes en el uso del recuento de referencias. Una de las más mencionadas es referencias circulares: supongamos que A referencias B, B referencias C y C referencias B. Si A fuera a soltar su referencia a B, tanto B como C seguirán teniendo un recuento de referencia de 1 y no serán borrados con el recuento de referencia tradicional. CPython (el recuento de referencias no es parte de Python, sino parte de la implementación C del mismo) capta referencias circulares con una rutina de recolección de elementos no utilizados que se ejecuta periódicamente ...

Otro inconveniente: El recuento de referencias puede hacer que la ejecución sea más lenta. Cada vez que se hace referencia y se quita la referencia a un objeto, el intérprete/VM debe verificar si el recuento se ha reducido a 0 (y luego desasignar si lo hizo). Garbage Collection no necesita hacer esto.

Además, la recolección de basura se puede hacer en un hilo separado (aunque puede ser un poco complicado). En máquinas con mucha RAM y para procesos que usan la memoria solo lentamente, es posible que no desee hacer GC en absoluto. El recuento de referencias sería un inconveniente en términos de rendimiento ...

+16

Una diferencia adicional importante es que el GC ansioso mediante recuento de referencias siempre utiliza memoria "mínima" (excepto en el caso de dependencia circular), mientras que el enfoque lento de Java puede hacer que la JVM use mucha más memoria de la que realmente se necesita. GC run lo vuelve a poner en línea. El enfoque de Java da velocidad a costa de la memoria y tiene la ventaja de que la memoria es abundante. Cuando es escaso, el enfoque de Python funcionará mejor. –

+1

El recuento de referencias es más lento que el marcado/barrido GC por un par de otras razones: 1. las escrituras de memoria para actualizar los recuentos de referencia son costosas y causan problemas de concurrencia ya que requieren sincronización. 2. Los recuentos de referencia utilizan memoria extra, lo que aumenta el tamaño del objeto y, por lo tanto, aumenta la presión de la memoria caché. – mikera

13

Darren Thomas da una buena respuesta. Sin embargo, una gran diferencia entre los enfoques de Java y Python es que con el recuento de referencias en el caso común (sin referencias circulares) los objetos se limpian inmediatamente en lugar de en una fecha posterior indeterminada.

Por ejemplo, puedo escribir código descuidado, no portátil en CPython como

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

y el descriptor de archivo para ese archivo abrí será limpiado de inmediato porque tan pronto como la referencia a la intemperie archivo desaparece, el archivo es basura recolectada y el descriptor de archivo se libera. Por supuesto, si ejecuto Jython o IronPython o posiblemente PyPy, el recolector de basura no se ejecutará necesariamente hasta mucho más tarde; posiblemente me quedaré sin descriptores de archivo primero y mi programa se bloqueará.

código por lo que debería estar escribiendo que se parece a

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

pero a veces la gente le gusta depender de recuento de referencias para siempre liberar sus recursos, ya que a veces puede hacer que el código un poco más corto.

Yo diría que el mejor recolector de basura es el que tiene el mejor rendimiento, que actualmente parece ser el basurero generacional al estilo Java que puede ejecutarse en un hilo separado y tiene todas estas optimizaciones locas, etc. las diferencias en la forma de escribir el código deberían ser insignificantes e idealmente inexistentes.

+4

Buena respuesta. Para mí, un punto importante de su respuesta no es el uso de un explícito 'close()' en el segundo ejemplo. Mucho menos código para escribir en python. Esto se explica en http://docs.python.org/howto/doanddont.html (busque "con abierto") –

2

La última máquina virtual Java de Sun tiene en realidad varios algoritmos de GC que puede ajustar.Las especificaciones de Java VM omitieron intencionalmente el comportamiento real del GC para permitir diferentes (y múltiples) algoritmos GC para diferentes máquinas virtuales.

Por ejemplo, para todas las personas que no les gusta el enfoque "pare el mundo" del comportamiento predeterminado del Sun Java VM GC, hay VM como IBM's WebSphere Real Time que permite que la aplicación en tiempo real se ejecute en Java.

Dado que la especificación Java VM está públicamente disponible, (en teoría) nada impide que alguien implemente una máquina virtual Java que use el algoritmo GC de CPython.

+0

Probablemente no permita el recuento de referencias simple (suponiendo que no puede agregar cosas al recuento de referencias probablemente no se filtre debajo de ningún circunstancia). No estoy seguro de cómo python lidia con esto, aunque creo que tiene al menos algún tipo de control de ciclos. –

2

El recuento de referencias es particularmente difícil de realizar eficientemente en un entorno de subprocesos múltiples. No sé cómo comenzarías a hacerlo sin entrar en transacciones asistidas por hardware o instrucciones atómicas similares (actualmente) inusuales.

El recuento de referencias es fácil de implementar. Las JVM han invertido mucho dinero en implementaciones competidoras, por lo que no debería sorprender que implementen soluciones muy buenas para problemas muy difíciles. Sin embargo, cada vez es más fácil orientar su idioma favorito a la JVM.

+0

Afortunadamente esto realmente no parece ser un problema en Python, lo que con el lamentable soporte de subprocesos, por lo que finalmente es un positivo para el GIL ... (A menos que esté en Jython/IPython/etc ofc) – Basic

5

La recolección de basura es más rápida (más eficiente en tiempo) que el recuento de referencias, si tiene suficiente memoria. Por ejemplo, una copia gc atraviesa los objetos "vivos" y los copia en un nuevo espacio, y puede reclamar todos los objetos "muertos" en un paso al marcar una región de memoria completa. Esto es muy eficiente, si tiene suficiente memoria. Las colecciones generacionales usan el conocimiento de que "la mayoría de los objetos mueren jóvenes"; a menudo solo se debe copiar un pequeño porcentaje de los objetos.

[Esta es también la razón por la GC puede ser más rápido que malloc/libre]

recuento de referencia es mucho más eficiente que el espacio de recogida de basura, ya que recupera la memoria el momento en que se pone fuera de cobertura. Esto es útil cuando desea adjuntar finalizadores a objetos (por ejemplo, para cerrar un archivo una vez que el objeto File no está disponible). Un sistema de conteo de referencias puede funcionar incluso cuando solo un pequeño porcentaje de la memoria es libre. Pero el costo de administración de tener que incrementar y disminuir contadores en cada asignación de puntero costaba mucho tiempo, y todavía se necesita algún tipo de recolección de basura para reclamar ciclos.

Por lo tanto, la contrapartida es clara: si tiene que trabajar en un entorno con limitaciones de memoria, o si necesita finalizadores precisos, utilice el recuento de referencias. Si tiene suficiente memoria y necesita la velocidad, use la recolección de basura.

26

En realidad, el recuento de referencias y las estrategias utilizadas por Sun JVM son todos tipos diferentes de algoritmos de recolección de basura.

Hay dos enfoques amplios para rastrear objetos muertos: seguimiento y recuento de referencias. Al rastrear el GC comienza desde las "raíces", cosas como las referencias a la pila, y rastrea todos los objetos alcanzables (en vivo). Todo lo que no se puede alcanzar se considera muerto. En el recuento de referencias cada vez que se modifica una referencia, el objeto involucrado tiene su conteo actualizado. Cualquier objeto cuya cuenta de referencia se establece en cero se considera muerto.

Con básicamente todas las implementaciones de GC hay intercambios, pero el rastreo suele ser bueno para una operación alta (es decir, rápida) pero tiene tiempos de pausa más largos (espacios más grandes donde la IU o el programa pueden congelarse). El recuento de referencias puede operar en trozos más pequeños, pero será más lento en general. Puede significar menos congelaciones pero peor rendimiento en general.

Además, un GC de recuento de referencias requiere un detector de ciclos para limpiar cualquier objeto en un ciclo que no será capturado solo por su recuento de referencias. Perl 5 no tenía un detector de ciclo en su implementación de GC y podría perder memoria que era cíclica.

La investigación también se ha hecho para obtener lo mejor de ambos mundos (los tiempos de baja de pausa, de alto rendimiento): http://cs.anu.edu.au/~Steve.Blackburn/pubs/papers/urc-oopsla-2003.pdf

1

Al final del partido, pero creo que una razón significativa para RC en Python es su simplicidad. Ver esto email by Alex Martelli, por ejemplo.

(No pude encontrar un enlace fuera de la caché de google, la fecha del correo electrónico desde el 13 de octubre de 2005 en la lista de Python).

+1

Creo que esto es el enlace equivocado – mtasic85

3

Una gran desventaja del GC de rastreo de Java es que de vez en cuando "parará el mundo" y congelará la aplicación por un tiempo relativamente largo para hacer un GC completo. Si el montón es grande y el árbol de objetos complejo, se congelará durante unos segundos. Además, cada GC completo visita todo el árbol de objetos una y otra vez, algo que probablemente sea bastante ineficiente. Otro inconveniente de la forma en que Java hace GC es que debe decirle a jvm qué tamaño de almacenamiento dinámico desea (si el valor predeterminado no es lo suficientemente bueno); la JVM deriva de ese valor varios umbrales que dispararán el proceso GC cuando hay demasiada acumulación de basura en el montón.

Supongo que esta es realmente la causa principal de la sensación desigual de Android (basada en Java), incluso en los teléfonos celulares más caros, en comparación con la suavidad de iOS (basada en ObjectiveC y usando RC).

Me encantaría ver una opción jvm para habilitar la gestión de memoria RC, y tal vez mantener GC solo para ejecutar como último recurso cuando ya no queda memoria.

+0

Java GC (recolección de basura) congela la ejecución cuando se ejecuta GC. Por lo tanto, no es adecuado para aplicaciones que deberían ser receptivas en todo momento donde no sea aceptable detener la ejecución. RC (Recuento de referencias) se usa en Objective C y Swift que se usa en dispositivos de apple. Entonces iOS se siente más receptivo. En resumen, los lenguajes de RC como Python, ObjectiveC, Swift, etc. tienen un rendimiento predecible y no se detienen para hacer GC. Java también toma demasiada memoria por adelantado para su almacenamiento, lo que priva a otras aplicaciones de RAM utilizable, ya que Python usa solo la memoria necesaria para ejecutar. – codefire

+0

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

+0

@codefire algún recurso de rastreo de gc no hará que se detenga el mundo. Azul Zing JVM lo ha implementado. Aquí está el algoritmo, http://www.azulsystems.com/sites/default/files/images/c4_paper_acm.pdf –

Cuestiones relacionadas