recolector que collector collection basura garbage-collection code-generation language-design

garbage-collection - collector - garbage collection que es



¿Cómo implementar cierres sin gc? (13)

Estoy diseñando un lenguaje. Primero, quiero decidir qué código generar. El idioma tendrá cierres léxicos y una herencia basada en prototipos similar a javascript. Pero no soy fan de gc y trato de evitar tanto como sea posible. Entonces, la pregunta: ¿existe una manera elegante de implementar cierres sin recurrir a asignar el marco de pila en el montón y dejarlo como recolector de basura?

Mis primeros pensamientos:

  1. Usa el recuento de referencias y la basura para recolectar los ciclos (realmente no me gusta esto)
  2. Utilice la pila de espagueti (se ve muy ineficiente)
  3. Limita la formación de cierres a algunos contextos de tal manera que, me puedo salir con una pila de direcciones de devolución y una pila de locales.

No usaré un lenguaje de alto nivel ni seguiré ninguna convención de llamadas, por lo que puedo destrozar la pila todo lo que quiera.

(Editar: Sé que el conteo de referencias es una forma de recolección de basura pero estoy usando gc en su significado más común)


Esta sería una mejor pregunta si puede explicar lo que intenta evitar al no usar GC. Como estoy seguro de que sabe, la mayoría de los lenguajes que proporcionan cierres léxicos los asignan en el montón y les permiten retener referencias a enlaces de variables en el registro de activación que los creó.

La única alternativa a ese enfoque que conozco es lo que usa gcc para las funciones anidadas: crear un trampolín para la función y asignarlo a la pila. Pero como dice el manual de gcc:

Si intenta llamar a la función anidada a través de su dirección después de que la función contenedora haya salido, todo el infierno se desatará. Si intentas llamar después de que ha salido un nivel de ámbito que lo contiene, y si se refiere a algunas de las variables que ya no están dentro del alcance, puede que tengas suerte, pero no es prudente correr el riesgo. Sin embargo, si la función anidada no se refiere a nada que haya salido del alcance, debe estar seguro.

La versión corta es, usted tiene tres opciones principales:

  • asigne cierres en la pila y no permita su uso una vez que su función contenedora finalice.
  • asignar cierres en el montón, y usar algún tipo de recolección de basura.
  • hacer una investigación original, tal vez a partir de las cosas de la región que ML, Cyclone, etc. tienen.

La especificación C ++ 0x define lambdas sin recolección de basura. En resumen, la especificación permite un comportamiento no determinista en los casos en que el cierre lambda contiene referencias que ya no son válidas. Por ejemplo (pseudo-sintaxis):

(int)=>int create_lambda(int a) { return { (int x) => x + a } } create_lambda(5)(4) // undefined result

La lambda en este ejemplo se refiere a una variable ( a ) que está asignada en la pila. Sin embargo, ese marco de pila se ha reventado y no está necesariamente disponible una vez que la función retorna. En este caso, probablemente funcionaría y devolvería 9 como resultado (suponiendo una sana semántica de compilación), pero no hay forma de garantizarlo.

Si está evitando la recolección de basura, entonces estoy asumiendo que también permite la asignación explícita de montones frente a la pila y (probablemente) punteros. Si ese es el caso, entonces puedes hacer como C ++ y simplemente asumir que los desarrolladores que usan tu lenguaje serán lo suficientemente inteligentes como para detectar los casos problemáticos con lambdas y copiarlos al montón explícitamente (como lo harías si estuvieras devolviendo un valor sintetizado en Una función).


O simplemente no hagas GC en absoluto. Puede haber situaciones en las que es mejor simplemente olvidar la fuga de memoria y dejar que el proceso se solucione después de que finalice.

Dependiendo de sus dudas sobre GC, puede tener miedo de los barridos periódicos de GC. En este caso, podría hacer un GC selectivo cuando un elemento se sale del alcance o cambia el puntero. Sin embargo, no estoy seguro de lo caro que sería.

@ Allen

¿De qué sirve un cierre si no puede usarlos cuando sale la función contenedora? Por lo que entiendo, ese es el objetivo de los cierres.


Si tiene la maquinaria para una GC de copiado precisa, podría asignarla inicialmente en la pila y copiarla en el montón y actualizar punteros si descubre en la salida que se ha escapado un puntero a este marco de pila. De esa forma, solo pagará si realmente captura un cierre que incluya este marco de pila. Si esto ayuda o duele depende de la frecuencia con la que use cierres y cuánto capturen.

También podría considerar el enfoque de C ++ 0x ( N1968 ), aunque como cabría esperar de C ++, consiste en contar con el programador para especificar qué se copia y a qué se hace referencia, y si se equivoca, solo obtiene accesos no válidos.


Usa el recuento de referencias y la basura para recolectar los ciclos (realmente no me gusta esto)

Es posible diseñar su idioma de modo que no haya ciclos: si solo puede hacer objetos nuevos y no mutar los viejos, y si hacer un objeto no puede hacer un ciclo, entonces los ciclos nunca aparecen. Erlang funciona esencialmente de esta manera, aunque en la práctica usa GC.


Crea múltiples stacks?


He leído que las últimas versiones de ML usan GC solo moderadamente


Podría trabajar asumiendo que todos los cierres se llamarán finalmente y exactamente una vez. Ahora, cuando se llame al cierre, puede hacer la limpieza en el momento del cierre.

¿Cómo planeas lidiar con la devolución de objetos? Deben limpiarse en algún momento, que es exactamente el mismo problema con los cierres.


Este hilo podría ayudar, aunque algunas de las respuestas aquí ya reflejan las respuestas.

Un cartel es un buen punto:

Parece que quieres recolección de basura para cierres "en ausencia de una verdadera recolección de basura". Tenga en cuenta que los cierres se pueden utilizar para implementar células cons. Entonces, su pregunta parece ser acerca de la recolección de basura "en ausencia de una verdadera recolección de basura": hay abundante literatura relacionada. Restringir el problema a los cierres realmente no lo cambia.

Entonces la respuesta es: no , no hay una manera elegante de tener cierres y sin GC real. Lo mejor que puedes hacer es piratear para restringir tus cierres a un tipo particular de cierre. Todo esto es innecesario si tienes un GC apropiado.

Entonces, mi pregunta refleja algunos de los otros aquí: ¿por qué no quieres implementar GC? Una simple marca + barrido o parada + copia toma alrededor de 2-300 líneas de código (Scheme), y no es realmente tan malo en términos de esfuerzo de programación. En términos de hacer tus programas más lentos:

  1. Puede implementar un GC más complejo que tenga un mejor rendimiento.
  2. Solo piense en todas las pérdidas de memoria que sufrirán los programas en su idioma.
  3. Codificar con un GC disponible es una bendición. (Piensa C #, Java, Python, Perl, etc. ... vs. C ++ o C).

Entiendo que llegué muy tarde, pero me encontré con esta pregunta por accidente.

Creo que el apoyo total a los cierres requiere GC, pero en algunos casos especiales la asignación de la pila es segura. La determinación de estos casos especiales requiere algún análisis de escape. Sugiero que eche un vistazo a los documentos de lenguaje de BitC , como la Implementación de cierre en BitC . (Aunque dudo si los documentos reflejan los planes actuales.) Los diseñadores de BitC tenían el mismo problema que tú. Decidieron implementar un modo especial no recopilable para el compilador, que niega todos los cierres que podrían escapar. Si está activado, restringirá el idioma significativamente. Sin embargo, la función aún no está implementada.

Te aconsejo que uses un colector: es la manera más elegante. También debe considerar que un recolector de basura bien construido asigna memoria más rápido que malloc. La gente de BitC realmente valora el rendimiento y todavía piensan que GC está bien incluso para la mayor parte de su sistema operativo, Coyotos. Puede migrar los inconvenientes por medios simples:

  • crea solo una cantidad mínima de basura
  • deje que el programador controle el colector
  • optimizar el uso de pila / pila mediante análisis de escape
  • usar un colector incremental o concurrente
  • si de alguna manera es posible, divida el montón como lo hace Erlang

Muchos temen a los recolectores de basura debido a sus experiencias con Java. Java tiene un coleccionista fantástico, pero las aplicaciones escritas en Java tienen problemas de rendimiento debido a la gran cantidad de basura generada. Además, un tiempo de ejecución inflado y una compilación de JIT elegante no es realmente una buena idea para las aplicaciones de escritorio debido a los tiempos de inicio y respuesta más largos.


Supongo que si el proceso es muy corto, lo que significa que no puede usar mucha memoria, entonces GC no es necesario. La situación es análoga a preocuparse por el desbordamiento de pila. No anide demasiado profundamente, y no puede desbordarse; no corras demasiado tiempo, y no puedes necesitar el GC. La limpieza se convierte en una cuestión de simplemente reclamar la gran región que ha asignado previamente. Incluso un proceso más largo se puede dividir en procesos más pequeños que tienen sus propios montones preasignados. Esto funcionaría bien con los controladores de eventos, por ejemplo. No funciona bien, si está escribiendo un compilador; en ese caso, un GC seguramente no es una gran desventaja.


Entonces, la pregunta: ¿existe una manera elegante de implementar cierres sin recurrir a asignar el marco de pila en el montón y dejarlo como recolector de basura?

GC es la única solución para el caso general.


¿Mejor tarde que nunca?

Puede encontrar esto interesante: Ejecución diferencial .

Es una estructura de control poco conocida, y su uso principal es la programación de interfaces de usuario, incluidas las que pueden cambiar dinámicamente durante el uso. Es una alternativa significativa al paradigma Modelo-Vista-Controlador.

Lo menciono porque uno podría pensar que dicho código dependería en gran medida de los cierres y la recolección de basura, pero un efecto secundario de la estructura de control es que elimina ambos, al menos en el código de UI.