2012-05-18 16 views
6

He estado experimentando con el diseño del lenguaje de programación, y he llegado al punto de necesitar implementar un sistema de recolección de basura. Ahora, lo primero que se me vino a la mente fue el recuento de referencias, pero esto no manejará los bucles de referencia. La mayoría de las páginas que encuentro cuando busco algoritmos son referencias sobre cómo ajustar los recolectores de basura en idiomas existentes, como Java. Cuando encuentro algo que describa algoritmos específicos, no obtengo los detalles suficientes para la implementación. Por ejemplo, la mayoría de las descripciones incluyen "cuando su programa tiene poca memoria ...", lo cual no es probable que suceda pronto en un sistema de 4 GB con mucho intercambio. Entonces, lo que estoy buscando son algunos tutoriales con buenos detalles de implementación, como cómo ajustar cuándo iniciar el recolector de elementos no utilizados (es decir, recopilar después de X cantidad de asignaciones de memoria, o cada Y minutos, etc.).¿Qué es un algoritmo simple de recolección de basura para experimentar con un intérprete simple?

Para dar un par de detalles sobre lo que estoy tratando de hacer, empiezo escribiendo un intérprete basado en la pila similar a Postscript, y mi próximo intento será probablemente un lenguaje de expresión S basado en uno de los dialectos Lisp. Estoy implementando en línea C. Mi meta es la autoeducación y documentar las diversas etapas en un tutorial sobre "cómo diseñar y escribir un intérprete".

En cuanto a lo que he hecho hasta ahora, he escrito un intérprete simple que implementa un lenguaje imperativo de estilo C, que es analizado y procesado por una VM de estilo máquina de pila (vea lang2e.sourceforge.net). Pero este lenguaje no asigna memoria nueva al ingresar ninguna función, y no tiene ningún tipo de datos de puntero, por lo que en realidad no existía la necesidad de ningún tipo de administración de memoria avanzada. Para mi próximo proyecto, estoy pensando en comenzar a contar referencias para objetos de tipo no puntero (enteros, cadenas, etc.) y luego seguir cualquier objeto tipo puntero (que pueda generar referencias circulares) en un grupo de memoria separado. . Luego, cada vez que la agrupación crezca más de X unidades de asignación más de lo que era al final del ciclo anterior de recolección de elementos no utilizados, inicie de nuevo el recopilador.

Mi requisito es que no sea demasiado ineficiente, pero fácil de implementar y documentar claramente (recuerde, quiero desarrollar esto en un papel o libro para que otros sigan). El algoritmo que tengo actualmente en la parte delantera es el marcado tricolor, pero parece que un colector generacional sería un poco mejor, pero más difícil de documentar y comprender. Así que estoy buscando un material de referencia claro (preferiblemente disponible en línea) que incluya suficientes detalles de implementación para comenzar.

+1

Google 'Mark and Sweep' –

+0

Debo añadir que he visto las descripciones de varios recolectores de basura, como las variaciones en la marca y el barrido, pero la mayoría de las páginas que he encontrado no eran mucho mejores que las Artículo de Wikipedia. Por ejemplo, como mencioné en la pregunta, dicen patearlo cuando la memoria baja. Bueno, eso no es probable que suceda en los sistemas modernos durante el tiempo de ejecución de la mayoría de los scripts ligeros, y aunque lo haga, no sería bueno usar toda la memoria del sistema antes de iniciar el colector. Detalles como esos son lo que estoy buscando. –

+0

http://doc.cat-v.org/inferno/concurrent_gc/ - debería ser más que suficiente de los detalles para implementarlo. –

Respuesta

4

Hay un gran libro sobre la recolección de basura. Se llama Recolección de basura: Algoritmos para administración de memoria dinámica automática, y es excelente. Lo he leído, así que no lo estoy recomendando solo porque puedes encontrarlo con Google. Míralo here.

Para el prototipado simple, use marcaje y barrido o cualquier colector de compactación no generacional, no incremental simple. Los recolectores incrementales son buenos solo si necesita proporcionar una respuesta "en tiempo real" de su sistema. Mientras su sistema tenga permitido retrasarse arbitrariamente mucho en un punto particular en el tiempo, no necesita uno incremental. Los recolectores generacionales reducen la sobrecarga promedio de recolección de basura con el gasto de asumir algo sobre los ciclos de vida de los objetos.

He implementado todos los recolectores de basura (generacionales/no generacionales, incrementales/no incrementales) y la depuración es bastante difícil. Debido a que desea centrarse en el diseño de su idioma, y ​​tal vez no tanto en la depuración de un recolector de basura más complejo, puede apegarse a uno simple. Yo iría por marcar y barrer lo más probable.

Cuando utiliza la recolección de basura, no necesita contar de referencia. Tirar a la basura.

+0

En cuanto a descartar el recuento de referencias, habrá varios objetos que son altamente transitorios, la mayoría de ellos temporalmente en la pila, por ejemplo "2 * 3 + 5" (o en orden RPN, "2 3 * 5 + "dejará" 6 "en la pila hasta que el operador de agregarlo lo consuma. Con solo marcar y barrer, parece que el GC se iniciará con bastante frecuencia. Sin embargo, ¿es esto aún más eficiente que la sobrecarga del recuento de refs? Hay otra optimización que debería mirar para estos objetos temporales? Gracias. –

+0

La recolección de basura es más rápida que el recuento de referencias si se puede hacer GC casi nunca. De hecho, la recolección de basura es incluso más rápida que la administración de memoria explícita (por ejemplo, nueva/eliminar o malloc/free) si tiene suficiente espacio extra en el montón. El recuento de referencias también consume memoria extra porque necesita almacenar el campo de recuento de referencia. Tenga en cuenta que normalmente no asignaría ningún entero, pero haría Preséntelos en el lugar, en lugar de punteros a objetos GC. –

+0

Ok, entonces me conformaré con usar la marca y el barrido, o detener y copiar, y tal vez expandirlo a un simple coleccionista generacional. Puedo ver cómo un recolector de dos generaciones podría ayudar con los objetos transitorios, especialmente con detener y copiar. Ahora, dado que planeo documentar cada etapa de mi progreso para formar un tutorial, ¿cree que debería comenzar con el recuento de referencias, y demostrar las deficiencias del mismo, y luego evolucionar a algo mejor en el informe? Asumo que puedo tener la misma interfaz genérica expuesta al resto del intérprete y enchufar diferentes GC según sea necesario. –

1

Cuando se inicia el asignador es probable que esté abierto: puede GC cuando falle la asignación de memoria, o puede GC cada vez que se suelta una referencia, o en cualquier punto intermedio.

Esperar hasta que no tenga otra opción puede significar que nunca se GC, si el código de ejecución está bastante bien contenido. O bien, puede introducir una pausa gigantesca en su entorno y demoler por completo el tiempo de respuesta o las animaciones o la reproducción de sonido.

La ejecución del GC completo en cada free() podría amortizar el costo en más operaciones, aunque todo el sistema puede correr más lento como resultado. Podrías ser más predecible, pero más lento en general.

Si desea probar la cosa mediante la limitación artificial de la memoria, simplemente puede ejecutar con límites de recursos muy restrictivos en su lugar. Ejecute ulimit -v 1024 y cada proceso generado por ese shell solo tendrá un megabyte de memoria para trabajar.

Cuestiones relacionadas