Pregunta

Python utiliza el método de recuento de referencias para manejar la vida útil del objeto.Por lo tanto, un objeto que ya no tiene uso será destruido inmediatamente.

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

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

¿Es esto mejor que el enfoque de Python?

¿Fue útil?

Solución

Existen inconvenientes al utilizar el recuento de referencias.Una de las más mencionadas son las referencias circulares:Supongamos que A hace referencia a B, B hace referencia a C y C hace referencia a B.Si A eliminara su referencia a B, tanto B como C seguirán teniendo un recuento de referencia de 1 y no se eliminarán con el recuento de referencia tradicional.CPython (el recuento de referencias no es parte de Python en sí, sino parte de su implementación en C) captura referencias circulares con una rutina de recolección de basura separada que 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 elimina la referencia a un objeto, el intérprete/VM debe verificar si el recuento ha bajado a 0 (y luego desasignar si así fue).La recolección de basura no necesita hacer esto.

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

Otros consejos

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

Existen dos enfoques generales para rastrear objetos muertos:rastreo y recuento de referencias.Al rastrear, el GC comienza desde las "raíces", cosas como referencias de pila, y rastrea todos los objetos accesibles (vivos).Todo lo que no se puede alcanzar se considera muerto.En el recuento de referencias, cada vez que se modifica una referencia, se actualiza el recuento de los objetos involucrados.Cualquier objeto cuyo recuento de referencias se establezca en cero se considera muerto.

Básicamente, en todas las implementaciones de GC existen compensaciones, pero el rastreo suele ser bueno para lograr un alto rendimiento (es decir,rápido) pero tiene tiempos de pausa más largos (espacios más grandes donde la interfaz de usuario o el programa pueden congelarse).El recuento de referencias puede funcionar en porciones más pequeñas, pero en general será más lento.Puede significar menos congelaciones pero un rendimiento general más deficiente.

Además, un GC de conteo de referencias requiere un detector de ciclos para limpiar cualquier objeto en un ciclo que no sea detectado únicamente por su conteo de referencias.Perl 5 no tenía un detector de ciclos en su implementación de GC y podía perder memoria que era cíclica.

También se han realizado investigaciones para obtener lo mejor de ambos mundos (tiempos de pausa bajos, alto rendimiento):http://cs.anu.edu.au/~Steve.Blackburn/pubs/papers/urc-oopsla-2003.pdf

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 y 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 que abrí se limpiará inmediatamente porque tan pronto como la referencia al archivo abierto desaparece, el archivo se recolecta como basura y el descriptor de archivo se libera.Por supuesto, si ejecuto Jython o IronPython o posiblemente PyPy, entonces el recolector de basura no necesariamente se ejecutará hasta mucho más tarde;Posiblemente primero me quede sin descriptores de archivos y mi programa falle.

Entonces DEBES escribir un código que se parezca a

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

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

Yo diría que el mejor recolector de basura es el que tiene mejor rendimiento, que actualmente parece ser el recolector de basura generacional estilo Java que puede ejecutarse en un subproceso 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.

Creo que el artículo "Teoría y práctica de Java:Una breve historia de la recolección de basura." de IBM debería ayudar a explicar algunas de las preguntas que tenga.

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

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

El recuento de referencias ahorra mucho más espacio que la recolección de basura, ya que recupera memoria en el mismo momento en que se vuelve inalcanzable.Esto es bueno cuando desea adjuntar finalizadores a objetos (p. ej.cerrar un archivo una vez que el objeto Archivo se vuelve inalcanzable).Un sistema de recuento de referencias puede funcionar incluso cuando sólo un pequeño porcentaje de la memoria está libre.Pero el costo de administración de tener que incrementar y disminuir los contadores en cada asignación de puntero cuesta mucho tiempo, y todavía se necesita algún tipo de recolección de basura para recuperar los ciclos.

Entonces la compensación es clara:Si tiene que trabajar en un entorno con memoria limitada o si necesita finalizadores precisos, utilice el recuento de referencias.Si tiene suficiente memoria y necesita velocidad, utilice la recolección de basura.

Una gran desventaja del GC de seguimiento de Java es que de vez en cuando "detendrá el mundo" y congelará la aplicación durante un tiempo relativamente largo para realizar un GC completo.Si el montón es grande y el árbol de objetos es 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 realiza GC es que debe decirle a la jvm qué tamaño de montón desea (si el valor predeterminado no es lo suficientemente bueno);la JVM deriva de ese valor varios umbrales que activarán el proceso de GC cuando haya demasiada basura acumulada en el montón.

Supongo que esta es en realidad la causa principal de la sensación entrecortada de Android (basado en Java), incluso en los teléfonos móviles más caros, en comparación con la fluidez de iOS (basado en ObjectiveC y usando RC).

Me encantaría ver una opción de jvm para habilitar la administración de memoria RC y tal vez mantener GC solo para ejecutarse como último recurso cuando no quede más memoria.

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

Por ejemplo, para todas las personas a las que no les gusta el enfoque de "detener el mundo" del comportamiento predeterminado de Sun Java VM GC, existen VM como WebSphere en tiempo real de IBM que permite que la aplicación en tiempo real se ejecute en Java.

Dado que la especificación de Java VM está disponible públicamente, no hay (teóricamente) nada que impida que alguien implemente una Java VM que utilice el algoritmo GC de CPython.

El recuento de referencias es particularmente difícil de realizar de manera eficiente en un entorno de subprocesos múltiples.No sé cómo podrías empezar 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 competitivas, por lo que no debería sorprender que implementen muy buenas soluciones a problemas muy difíciles.Sin embargo, cada vez es más fácil orientar su idioma favorito en la JVM.

Al final del juego, pero creo que una razón importante para RC en Python es su simplicidad.Mira esto correo electrónico de Alex Martelli, Por ejemplo.

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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top