X-Git-Url: https://git.llucax.com/z.facultad/75.00/informe.git/blobdiff_plain/a12d176928b68dd84292540dba539cdb75e6f102..e7b528ca717cea5b878e30fbb2765a6fbad2bf7c:/source/dgc.rst?ds=sidebyside diff --git a/source/dgc.rst b/source/dgc.rst index f3327bd..a0b9ce5 100644 --- a/source/dgc.rst +++ b/source/dgc.rst @@ -1227,85 +1227,276 @@ asignación de memoria. Problemas y limitaciones ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -TODO - - - - -Como se ha visto, D_ es un lenguaje de programación muy completo, pero aún -tiene algunos aspectos inconclusos. Su recolector de basura está en un estado -de evolución muy temprana. Se trata de un marcado y barrido (*mark and sweep*) -conservativo que, en ciertas circunstancias, no se comporta como es debido, ya -que revisa toda la memoria del programa en busca de referencias a objetos en -el *heap* (en vez de revisar sólo las partes que almacenan punteros). Esto -produce que, en ciertos casos, por ejemplo al almacenar arreglos de número -o *strings* en la pila, el recolector de basura se encuentre con *falsos -positivos*, pensando que un área del *heap* está siendo utilizada cuando en -realidad el puntero que hacía referencia a ésta no era tal. Este efecto puede -llevar a la pérdida de memoria masiva, llegando al límite de que eventualmente -el sistema operativo tenga que matar al programa por falta de memoria -[DNG46407]_. Aún cuando el programa no tenga estos problemas de por sí, por -usar datos que no pueden ser confundidos con direcciones de memoria, este -problema podría ser explotado por ataques de seguridad, inyectando valores que -sí sean punteros válidos y provocando el efecto antes mencionado que deriva en -la terminación abrupta del programa [DNG35364]_. Finalmente, a estos problemas -se suman los problemas de *performance* [DNG43991]_. - -Es difícil que D_ pueda ser un lenguaje de programación exitoso si no provee un -recolector de basura eficiente y que realmente evite la pérdida masiva de -memoria. Por otro lado, D_ podría atraer a una base de usuarios mucho más -amplia, si la gama de estrategias de recolección es más amplia, pudiendo lograr -adaptarse a más casos de uso sin llegar al límite de tener que caer en el -manejo explícito de memoria y perder por completo las ventajas de la -recolección de basura (con la consecuencia ya mencionada de que el manejo de -memoria tenga que pasar a ser parte de las interfaces y la complejidad que esto -agrega al diseño -y uso- de una biblioteca). - - +A continuación se presentan los principales problemas encontrados en la +implementación actual del recolector de basura de D_. Estos problemas surgen +principalmente de la observación del código y de aproximadamente 3 años de +participación y observación del grupo de noticias, de donde se obtuvieron los +principales problemas percibidos por la comunidad que utiliza el lenguaje. + + +Complejidad del código y documentación +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +El análisis del código fue muy complicado debido a la falta de documentación +y desorganización del código. Además se nota que el recolector ha sido escrito +en una fase muy temprana y que a ido evolucionando a partir de ello de forma +desprolija y sin ser rescrito nunca para aprovechar las nuevas características +que el lenguaje fue incorporando (por ejemplo *templates*). + +Estos dos problemas (código complicado y falta de documentación) producen un +efecto de círculo vicioso, porque provocan que sea complejo entender el +recolector actual y en consecuencia sea muy complicado escribir documentación +o mejorarlo. Esto a su vez provoca que, al no disponer de una implementación +de referencia sencilla, sea muy difícil implementar un recolector nuevo. + +Este es, probablemente, la raíz de todos los demás problemas del recolector +actual. Para ilustrar la dimensión del problema se presenta la implementación +real de la función ``bigAlloc()``:: + + /** + * Allocate a chunk of memory that is larger than a page. + * Return null if out of memory. + */ + void *bigAlloc(size_t size) + { + Pool* pool; + size_t npages; + size_t n; + size_t pn; + size_t freedpages; + void* p; + int state; + + npages = (size + PAGESIZE - 1) / PAGESIZE; + + for (state = 0; ; ) + { + // This code could use some refinement when repeatedly + // allocating very large arrays. + + for (n = 0; n < npools; n++) + { + pool = pooltable[n]; + pn = pool.allocPages(npages); + if (pn != OPFAIL) + goto L1; + } + + // Failed + switch (state) + { + case 0: + if (disabled) + { state = 1; + continue; + } + // Try collecting + freedpages = fullcollectshell(); + if (freedpages >= npools * ((POOLSIZE / PAGESIZE) / 4)) + { state = 1; + continue; + } + // Release empty pools to prevent bloat + minimize(); + // Allocate new pool + pool = newPool(npages); + if (!pool) + { state = 2; + continue; + } + pn = pool.allocPages(npages); + assert(pn != OPFAIL); + goto L1; + case 1: + // Release empty pools to prevent bloat + minimize(); + // Allocate new pool + pool = newPool(npages); + if (!pool) + goto Lnomemory; + pn = pool.allocPages(npages); + assert(pn != OPFAIL); + goto L1; + case 2: + goto Lnomemory; + default: + assert(false); + } + } + + L1: + pool.pagetable[pn] = B_PAGE; + if (npages > 1) + cstring.memset(&pool.pagetable[pn + 1], B_PAGEPLUS, npages - 1); + p = pool.baseAddr + pn * PAGESIZE; + cstring.memset(cast(char *)p + size, 0, npages * PAGESIZE - size); + debug (MEMSTOMP) cstring.memset(p, 0xF1, size); + //debug(PRINTF) printf("\tp = %x\n", p); + return p; + + Lnomemory: + return null; // let mallocNoSync handle the error + } + +Se recuerda que la semántica de dicha función es la misma que la de la función +``new_big()`` presentada en :ref:`dgc_algo_alloc`. + +Además, como se comentó en la sección anterior, los algoritmos en la +implementación real están considerablemente menos modularizados que los +presentados en la sección :ref:`dgc_algo`. Por ejemplo, la función +``fullcollect()`` son 300 líneas de código. -Soluciones Propuestas -Para poder implementar un recolector de basura no conservativo es necesario -disponer de un soporte de reflexión (en tiempo de compilación [DNG44607]_ y de -ejecución [DNG29291]_) bastante completo . De otra forma es imposible -distinguir si un área de memoria de la pila es utilizada como un puntero o como -un simple conjunto de datos. D_ provee algún grado de reflexión, pero muy -limitado como para poder obtener este tipo de información. Ya hay un plan para -agregar mayores capacidades de reflexibilidad [DNG6842]_, y un pequeño avance -en este sentido en la `versión 1.001`_, pero con algunos problemas [DNG6890]_ -[DNG6893]_. - -.. _`versión 1.001`: http://www.digitalmars.com/d/changelog.html#new1_001 +Memoria *encomendada* +^^^^^^^^^^^^^^^^^^^^^ +Como se comentó en la sección anterior, diferenciar entre memoria +*encomendada* de memoria *no-encomendada* es complejo y levemente costoso (en +particular para sistemas operativos que no hacen esta distinción, al menos +explícitamente, donde no hay ningún beneficio en realizar esta distinción). + +Incluso para Microsoft Windows, la ventaja de realizar esta distinción es +discutible. + + +Precisión +^^^^^^^^^ +Este fue historicamente uno de los problemas principales del recolector de D_ +[NGD46407]_ [NGD35364]_. Sin embargo, desde que, en la versión 1.001, se ha +incorporado la capacidad de marcar un bloque como de datos puros (no contiene +punteros, el atributo ``NO_SCAN``) [NGA6842]_, la gravedad de esos problemas ha +disminuído considerablemente, aunque siguieron reportándose problemas más +esporádicamente [NGD54084]_ [NGL13744]_. + +De todas maneras queda mucho lugar para mejoras, y es un tema recurrente en el +grupo de noticias de D_ y se han discutido formas de poder hacer que, al menos +el *heap* sea preciso [NGD44607]_ [NGD29291]_. Además se mostro un interés +general por tener un recolector más preciso [NGDN87831]_, pero no han habido +avances al respecto. + +Otra forma de minimizar los efectos de la falta de precisión que se ha +sugerido reiteradamente en el grupo es teniendo la +posibilidad de indicar cuando no pueden haber punteros interiores a un bloque +[NGD89394]_ [NGD71869]_. Esto puede ser de gran utilidad para objetos grandes +y en particular para mejorar la implementación de de arreglos asociativos. + + +Referencias débiles +^^^^^^^^^^^^^^^^^^^ +El recolector actual no dispone de soporte de *referencias débiles* +[#dgcweakref]_, sin embargo hay una demanda [NGD86840]_ [NGD13301]_ [NGL8264]_ +[NGD69761]_ [NGD74624]_ [NGD88065]_ -Se han propuesto otros métodos e implementaciones de recolector de basura, por -ejemplo colectores con movimiento (*moving collectors*) [DNG42557]_ y conteo de -referencias [DNG38689]_. Pero D_ es un lenguaje muy particular en cuanto a la -recolección de basura (al permitir :ref:d_low_level hay muchas consideraciones -a las que otros lenguajes no deben enfrentarse) y no es sencillo pensar en -otras implementaciones sin hacer modificaciones de base al lenguaje. +.. [#dgcweakref] Una referencia débil (o *weak reference* en inglés) es + aquella que que no protege al objeto referenciado de ser reciclado por el + recolector. +Para cubrir esta demanda, se han implementado soluciones como biblioteca para +suplir la inexistencia de una implementación oficial [NGA9103]_. +Sin embargo éstas son en general poco robustas y extremadamente dependientes +de la implementación del recolector y, en general, presentan problemas muy +sutiles [NGD88065]_. Por esta razón se ha discutido la posibilidad de incluir +la implementación de *referencias débiles* como parte del lenguaje +[NGD88559]_. -Problemas para Implementar Colectores con Movimiento -El principal problema es la capacidad de D_ de manipular punteros y otras -estructuras de bajo nivel, como uniones. O incluso la capacidad de interactuar -con C. Al mover un objeto de un área de memoria a otro, es necesario actualizar -todos los punteros que apuntan a éste. En D_ esta tarea no es trivial -[DNG42564]_ +Concurrencia +^^^^^^^^^^^^ +El soporte actual de concurrencia, en todos sus aspectos, es muy primitivo. El +recolector apenas soporta múltiples *mutators* pero con un nivel de +sincronización excesivo. +Se ha sugerido en el pasado el uso de *pools* y listas de libres específicos +de hilos, de manera de disminuir la contención, al menos para la asignación de +memoria [NGD75952]_ [NGDN87831]_. +Además se ha mostrado un interés por tener un nivel de concurrencia aún mayor +en el recolector, para aumentar la concurrencia en ambientes *multi-core* en +general pero en particular para evitar grandes pausas en programas con +requerimientos de tiempo real, historicamente una de las principales críticas +al lenguaje [NGDN87831]_ [NGL3937]_ [NGD22968]_ [NGA15246]_ [NGD5622]_ +[NGD2547]_ [NGD18354]_. -Problemas para Implementar Conteo de Referencias -Este tipo de recolectores reparten la carga de la recolección de forma uniforme -a lo largo (y a la par) de la ejecución del programa. El problema principal -para implementar este tipo de recolección es la necesidad de soporte en el -compilador (cada asignación debe ser acompañada por el incremento/decremento de -contadores de referencia), a menos que se implemente en una biblioteca. Por -otro lado, características como el rebanado de arreglos (ver :ref:d_high_level) -son difíciles de proveer con el conteo de referencias, entre otros problemas -[DNG38704]_. +Finalización +^^^^^^^^^^^^ +El recolector actual no garantiza la finalización de objetos. En particular +los objetos no son finalizados (es decir, no se llama a sus destructores) +si aún alcanzables desde el *root set* cuando el programa termina. Cabe +destacar que esto puede darse porque hay una referencia real desde el *root +set* (en cuyo caso queda bajo el control del usuario) pero también, dado que +el *root set* se visita de forma conservativa, se puede deber a un falso +positivo, en cuyo caso la omisión de la finalización queda por completo fuera +del control del usuario (y lo que es aún peor, el usuario no puede ser +siquiera notificado de esta anomalía). + +Si bien la especificación de D_ no requiere esta capacidad (de hecho, +rigurosamente hablando la especificación de D_ no garantiza la finalización de +objetos bajo ninguna circunstancia), no hay mayores problemas para implementar +un recolector que de este tipo de garantías [NGD88298]_. + +Además los objetos pueden ser finalizados tanto determinísticamente +(utilizando ``delete`` o ``scope``; ver secciones :ref:`d_low_level` +y :ref:`d_dbc`) como no deterministicamente (cuando son finalizados por el +recolector). En el primer caso se puede, por ejemplo, acceder sus atributos +u otra memoria que se conozca *viva*, mientras que en el segundo no. Sin +embargo un destructor no puede hacer uso de esta distinción, haciendo que la +finalización determinística tenga a fines prácticos las mismas restricciones +que la finalización no deterministica. Es por esto que se ha sugerido permitir +al destructor distinguir estos dos tipos de finalización [NGD89302]_. + + +Eficiencia +^^^^^^^^^^ +La eficiencia en general del recolector es una de las críticas frecuentes. Si +bien hay muchos problemas que han sido resueltos, en especial por la inclusión +de un mínimo grado de precisión en la versión 1.001, en la actualidad se +siguen encontrando en el grupo de noticias críticas respecto a esto +[NGD43991]_ [NGD67673]_ [NGD63541]_ [NGD90977]_. + +La principal causa de la ineficiencia del recolector actual es, probablemente, +lo simple de su algoritmo principal de recolección. Más allá de una +organización del *heap* moderadamente apropiada y de utilizar conjuntos de +bits para la fase de marcado, el resto del algoritmo es casi la versión más +básica de marcado y barrido. Hay mucho lugar para mejoras en este sentido. + + +Detalles +^^^^^^^^ +Finalmente hay varios detalles en la implementación actual que podrían +mejorarse: + +Listas de libres: + hay 12 listas de libres, como para guardar bloques de tamaño de ``B_16`` + a ``B_2048``, ``B_PAGE``, ``B_PAGEPLUS``, ``B_UNCOMMITTED`` y ``B_FREE``; + sin embargo solo tienen sentido los bloques de tamaño ``B_16`` a ``B_2048``, + por lo que 4 de esas listas no se utilizan. + +Conjuntos de bits: + los indicadores para la fase de marcado y otras propiedades de un bloque son + almacenados en conjuntos de bits que almacenan los indicadores de todos los + bloques de un *pool*. Como un *pool* tiene páginas con distintos tamaños de + bloque, se reserva una cantidad de bits igual a la mayor cantidad posible de + bloques que puede haber en el *pool*; es decir, se reserva 1 bit por cada 16 + bytes del *pool*. Para un *pool* de 1 MiB (tamaño mínimo), teniendo en + cuenta que se utilizan 5 conjuntos de bits (``mark``, ``scan``, ``finals``, + ``freebits`` y ``noscan``), se utilizan 40 KiB de memoria para conjuntos de + bits (un 4% de *desperdicio* si, por ejemplo, ese *pool* estuviera destinado + por completo a albergar un solo objeto grande; lo que equivaldría al 2560 + objetos de 16 bytes desperdiciados en bits inutilizados). + +Repetición de código: + Hay algunos fragmentos de código repetidos inecesariamente. Por ejemplo en + varios lugares se utilizan arreglos de tamaño variable que se implementan + repetidas veces (en general como un puntero al inicio del arreglo más el + tamaño actual del arreglo más el tamaño de la memoria total asignada + actualmente). Esto es propenso a errores y difícil de mantener. + +Uso de señales: + el recolector actual utiliza las señales del sistema operativo ``SIGUSR1`` + y ``SIGUSR2`` para pausar y reanudar los hilos respectivamente. Esto + puede traer incovenientes a usuarios que desean utilizar estas + señales en sus programas (o peor aún, si interactúan con bibliotecas + de C que hacen uso de estas señales) [NGD5821]_. .. include:: links.rst