2 .. Acá va lo que decidí hacer en base al análisis anterior y sus razones.
9 ============================================================================
11 Como hemos visto en :ref:`dgc_bad`, la mejora del recolector de basura puede
12 ser abordada desde múltiples flancos. Por lo tanto, para reducir la cantidad
13 de posibilidades hay que tener en cuenta uno de los principales objetivos de
14 este trabajo: encontrar una solución que tenga una buena probabilidad de ser
15 adoptada por el lenguaje, o alguno de sus compiladores al menos. Para asegurar
16 esto, la solución debe tener un alto grado de aceptación en la comunidad, lo
17 que implica algunos puntos claves:
19 * La eficiencia general de la solución no debe ser notablemente peor, en
20 ningún aspecto, que la implementación actual.
21 * Los cambios no deben ser drásticos.
22 * La solución debe atacar de forma efectiva al menos uno de los problemas
23 principales del recolector actual.
25 Bajo estos requerimientos, se concluye que probablemente el área más fértil
26 para explorar sea la falta de concurrencia por cumplir todos estos puntos:
28 * Si bien hay evidencia en la literatura sobre el incremento del tiempo de
29 ejecución total de ejecución de un programa al usar algoritmos concurrentes,
30 éste no es, en general, muy grande comparativamente.
31 * Existen algoritmos de recolección concurrente que no requieren ningún grado
32 de cooperación por parte del lenguaje o el compilador.
33 * La falta de concurrencia y los largos tiempos de pausa es una de las
34 críticas más frecuentes al recolector actual por parte de la comunidad.
36 A pesar de ser la concurrencia la veta principal a explorar en este trabajo,
37 se intenta abordar los demás problemas planteados siempre que sea posible
38 hacerlo sin alejarse demasiado del objetivo principal.
42 ----------------------------------------------------------------------------
44 Teniendo en cuenta que uno de los objetivos principales es no empeorar la
45 eficiencia general de forma notable, la confección de un banco de pruebas es
46 un aspecto fundamental, para poder comprobar con cada cambio que la eficiencia
47 final no se vea notablemente afectada.
49 La confección de un banco de pruebas no es una tarea trivial, mucho menos para
50 un lenguaje con el nivel de fragmentación que tuvo D_ (que hace que a fines
51 prácticos hayan 3 versiones del lenguaje compitiendo), y cuya masa crítica de
52 usuarios es de aficionados que usualmente abandonan los proyectos, quedando
53 obsoletos rápidamente.
55 Con el objetivo de confeccionar este banco de pruebas, desde el comienzo del
56 trabajo se han recolectado (usando como fuente principalmente el grupo de
57 noticias de D_ [#benchmod]_) programas triviales sintetizados con el único
58 propósito de mostrar problemas con el recolector de basura. Otros programas de
59 este estilo fueron escritos explícitamente para este trabajo.
61 Además se han recolectado [#benchmod]_ algunos pequeños programas portados de
62 otros lenguajes de programación, que si bien son pequeños y tienen como
63 objetivo ejercitar el recolector de basura, son programas reales que resuelven
64 un problema concreto, lo que otorga un juego de pruebas un poco más amplio que
65 los programas triviales.
67 .. [#benchmod] Cabe destacar que en general todos los programas recolectados
68 han sido modificados levemente para ajustarlos mejor a las necesidades del
69 banco de prueba (entre las modificaciones más frecuentes se encuentran la
70 conversión de Phobos_ a Tango_ y la eliminación de mensajes por salida
73 Pero probablemente lo más importante para confeccionar un banco de pruebas
74 verdaderamente útil es disponer de programas reales, que hayan sido diseñados
75 con el único objetivo de hacer su trabajo, sin pensar en como impacta el
76 recolector sobre ellos (ni ellos sobre el recolector). Estos programas proveen
77 las pruebas más realistas y amplias. Desgraciadamente no hay muchos programas
78 reales escritos en D_ disponibles públicamente, y no se encontró en la
79 comunidad tampoco una muestra de voluntad por compartir programas privados
80 para usar como banco de pruebas en este trabajo.
82 Por lo tanto el banco de pruebas que se conformó como una mezcla de estas tres
87 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
89 Este es el juego de programas triviales, escritos con el único objetivo de
90 ejercitar un área particular y acotada del recolector.
95 Su objetivo es ejercitar la manipulación de arreglos de tamaño considerable
96 que almacenan objetos de tamaño pequeño o mediano. Esta prueba fue hallada__
97 en el grupo de noticias de D_ y escrita por Babele Dunnit y aunque
98 originalmente fue concebido para mostrar un problema con la concatenación de
99 arreglos (como se aprecia en la sentencia ``version(loseMemory)``), ejercita
100 los aspectos más utilizados del del recolector: manipulación de arreglos
101 y petición e memoria. Es una de las pruebas que más estresa al recolector ya
102 que todo el trabajo que realiza el programa es utilizar servicios de éste.
104 El código fuente del programa es el siguiente::
112 Individual[20] children;
119 foreach (inout individual; individuals)
120 individual = new Individual;
122 Individual[N1] individuals;
125 version = loseMemory;
127 int main(char[][] args)
130 Population testPop1 = new Population;
131 Population testPop2 = new Population;
133 for (int i = 0; i < IT; i++) {
136 version (loseMemory) {
137 indi[] = testPop1.individuals ~ testPop2.individuals;
139 version (everythingOk) {
140 indi[0..N1] = testPop1.individuals;
141 indi[N1..N2] = testPop2.individuals;
147 __ http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D&article_id=54084
150 ``concpu`` y ``conalloc``
151 ^^^^^^^^^^^^^^^^^^^^^^^^^
152 Estos dos programas fueron escritos especialmente para este trabajo con el fin
153 de ejercitar la interacción entre el recolector y un *mutator* con varios
154 hilos. La única diferencia entre ellos es que ``concpu`` lanza hilos que hacen
155 trabajar de forma intensiva el procesador pero que no utilizan servicios del
156 recolector, salvo en el hilo principal, mientras que ``conalloc`` utiliza
157 servicios del recolector en todos los hilos lanzados.
159 El objetivo de estos programas es medir el impacto de las pausas del
160 recolector. Se espera medir dos tipos de pausa principales, por un lado el
161 tiempo máximo de pausa total, que puede involucrar a más de un hilo y por otro
162 el tiempo de *stop-the-world*, es decir, el tiempo en que los hilos son
163 efectivamente pausados por el recolector para tomar una *foto* de la pila
164 y registros para agregarlos al *root set*.
166 Se espera ``concpu`` sea capaz de explotar cualquier reducción en el tiempo de
167 *stop-the-world*, ya que los hilos solo son interrumpidos por este tipo de
168 pausa. Por otro lado, se espera que ``conalloc`` sea afectado por el tiempo
169 máximo de pausa, que podrían sufrir los hilos incluso cuando el *mundo* sigue
170 su marcha, debido al *lock* global del recolector y que los hilos usan
173 El código de ``concpu`` es el siguiente::
175 import tango.core.Thread: Thread;
176 import tango.core.Atomic: Atomic;
177 import tango.io.device.File: File;
178 import tango.util.digest.Sha512: Sha512;
179 import tango.util.Convert: to;
184 Atomic!(int) running;
186 void main(char[][] args)
188 auto fname = args[0];
192 NT = to!(int)(args[2]);
194 N = to!(int)(args[1]);
197 BYTES = cast(ubyte[]) File.get(fname);
198 auto threads = new Thread[NT];
199 foreach(ref thread; threads) {
200 thread = new Thread(&doSha);
203 while (running.load()) {
204 auto a = new void[](BYTES.length / 4);
205 a[] = cast(void[]) BYTES[];
208 foreach(thread; threads)
214 auto sha = new Sha512;
215 for (size_t i = 0; i < N; i++)
220 El código de ``conalloc`` es igual excepto por la función ``doSha()``, que es
221 de la siguiente manera::
225 for (size_t i = 0; i < N; i++) {
226 auto sha = new Sha512;
235 Escrito por David Schima y también hallado__ en el grupo de noticias de D_,
236 este programa pretende mostrar como afecta el *lock* global del recolector
237 en ambientes *multi-core*, incluso cuando a simple vista parecen no utilizarse
238 servicios del recolector::
240 import tango.core.Thread;
244 enum { nThreads = 4 };
245 auto threads = new Thread[nThreads];
246 foreach (ref thread; threads) {
247 thread = new Thread(&doAppending);
250 foreach (thread; threads)
257 for (size_t i = 0; i < 1_000_000; i++)
261 __ http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D&article_id=103563
263 El secreto está en que la concatenación de arreglos utiliza por detrás
264 servicios del recolector, por lo tanto un programa multi-hilo en el cual los
265 hilos (aparentemente) no comparten ningún estado, se puede ver
266 considerablemente afectado por el recolector (siendo este efecto más visible
267 en ambientes *multi-core* por el nivel de sincronización extra que significa
268 a nivel de *hardware*). Cabe destacar que, sin embargo, en Linux_ no es tan
274 Este programa trivial lee un archivo de texto y genera un arreglo de cadenas
275 de texto resultantes de partir el texto en palabras. Fue escrito por Leonardo
276 Maffi y también hallado__ en el grupo de noticias de D_. Su objetivo era
277 mostrar lo ineficiente que puede ser concatenar datos a un mismo arreglo
278 repetidas veces y ha desembocado en una pequeña `optimización`__ que sirvió
279 para apalear el problema de forma razonablemente efectiva.
281 El código es el siguiente::
283 import tango.io.device.File: File;
284 import tango.text.Util: delimit;
285 import tango.util.Convert: to;
287 int main(char[][] args) {
290 auto txt = cast(byte[]) File.get(args[1]);
291 auto n = (args.length > 2) ? to!(uint)(args[2]) : 1;
296 auto words = delimit!(byte)(txt, cast(byte[]) " \t\n\r");
297 return !words.length;
300 __ http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D&article_id=67673
301 __ http://d.puremagic.com/issues/show_bug.cgi?id=1923
306 Este programa fue escrito por Oskar Linde y nuevamente hallado__ en el grupo
307 de noticias. Fue construido para mostrar como el hecho de que el recolector
308 sea conservativo puede hacer que al leer datos binarios hayan muchos *falsos
309 punteros* que mantengan vivas celdas que en realidad ya no deberían ser
310 accesibles desde el *root set* del grafo de conectividad.
312 __ http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D&article_id=46407
314 El código del programa es el siguiente::
316 import tango.math.random.Random;
318 const IT = 125; // number of iterations, each creates an object
319 const BYTES = 1_000_000; // ~1MiB per object
320 const N = 50; // ~50MiB of initial objects
324 C c; // makes the compiler not set NO_SCAN
325 long[BYTES/long.sizeof] data;
329 auto rand = new Random();
332 foreach (ref o; objs) {
334 foreach (ref x; o.data)
337 for (int i = 0; i < IT; ++i) {
339 foreach (ref x; o.data)
341 // do something with the data...
348 Este programa está basado en la prueba de nombre ``binary-trees`` de `The
349 Computer Language Benchmarks Game`__, una colección de 12 programas escritos
350 en alrededor de 30 lenguajes de programación para comparar su eficiencia
351 (medida en tiempo de ejecución, uso de memoria y cantidad de líneas de
352 código). De este juego de programas se utilizó solo ``binary-trees`` por ser
353 el único destinado a ejercitar el manejo de memoria. El programa sólo manipula
354 árboles binarios, creándolos y recorriéndolos inmediatamente (no realiza
355 ningún trabajo útil). La traducción a D_ fue realizada por Andrey Khropov
356 y fue hallada__ en el grupo de noticias.
358 __ http://shootout.alioth.debian.org/
359 __ http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D&article_id=43991
361 El código fuente es el siguiente::
363 import tango.util.Convert;
366 int main(string[] args)
368 int N = args.length > 1 ? to!(int)(args[1]) : 1;
370 int maxDepth = (minDepth + 2) > N ? minDepth + 2 : N;
371 int stretchDepth = maxDepth + 1;
372 int check = TreeNode.BottomUpTree(0, stretchDepth).ItemCheck;
373 TreeNode longLivedTree = TreeNode.BottomUpTree(0, maxDepth);
374 for (int depth = minDepth; depth <= maxDepth; depth += 2) {
375 int iterations = 1 << (maxDepth - depth + minDepth);
377 for (int i = 1; i <= iterations; i++) {
378 check += TreeNode.BottomUpTree(i, depth).ItemCheck;
379 check += TreeNode.BottomUpTree(-i, depth).ItemCheck;
387 TreeNode left, right;
390 this(int item, TreeNode left = null, TreeNode right = null)
397 static TreeNode BottomUpTree(int item, int depth)
400 return new TreeNode(item,
401 BottomUpTree(2 * item - 1, depth - 1),
402 BottomUpTree(2 * item, depth - 1));
403 return new TreeNode(item);
409 return item + left.ItemCheck() - right.ItemCheck();
416 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
418 Todos los pequeños programas utilizados como parte del banco de prueba
419 provienen del `Olden Benchmark`__ [CAR95]_. Estos programas fueron diseñados
420 para probar el lenguaje de programación Olden__; un lenguaje diseñado para
421 paralelizar programas automáticamente en arquitecturas con memoria
422 distribuida. Son programas relativamente pequeños (entre 400 y 1000 líneas de
423 código fuente cada uno) que realizan una tarea secuencial que aloca
424 estructuras de datos dinámicamente. Las estructuras están usualmente
425 organizadas como listas o árboles, y muy raramente como arreglos. Los
426 programas pasan la mayor parte del tiempo alocando datos y el resto usando los
427 datos alocados, por lo que en general están acotados en tiempo por el uso de
428 memoria (y no de procesador).
430 __ http://www.irisa.fr/caps/people/truong/M2COct99/Benchmarks/Olden/Welcome.html
431 __ http://www.martincarlisle.com/olden.html
433 La traducción a D_ fue realizada por Leonardo Maffi y están basadas a su vez
434 en la traducción de este juego de pruebas a Java_, JOlden__ [CMK01]_. En
435 general (salvo para el programa ``voronoï``) está disponible el código fuente
436 portado a D_, Java_ y Python_, e incluso varias versiones con distintas
437 optimizaciones para reducir el consumo de tiempo y memoria. Además provee
438 comparaciones de tiempo entre todas ellas. Los programas utilizados en este
439 banco de pruebas son la versión traducida más literalmente de Java_ a D_, ya
440 que hace un uso más intensivo del recolector que las otras versiones.
442 __ http://www-ali.cs.umass.edu/DaCapo/benchmarks.html
444 A continuación se da una pequeña descripción de cada uno de los 5 programas
445 traducidos y los enlaces en donde encontrar el código fuente (y las
446 comparaciones de tiempos estar disponibles).
451 Este programa computa las interacciones gravitatorias entre un número
452 :math:`N` de cuerpos en tiempo :math:`O(N log N)` y está basado en árboles
453 heterogéneos de 8 ramas, según el algoritmo descripto por Barnes & Hut
456 Código fuente disponible en:
457 http://www.fantascienza.net/leonardo/js/dolden_bh.zip
462 Este programa ordena :math:`N` números, donde :math:`N` es una potencia de 2,
463 usando un ordenamiento *Bitonic* adaptativo, un algoritmo paralelo óptimo para
464 computadoras con memoria compartida, según describen Bilardi & Nicolau
465 [BN98]_. Utiliza árboles binarios como principal estructuras de datos.
467 Código fuente disponible en:
468 http://www.fantascienza.net/leonardo/js/dolden_bisort.zip
473 Este programa modela la propagación de ondas electromagnéticas a través de
474 objetos en 3 dimensiones. Realiza un cálculo simple sobre un grafo irregular
475 bipartito (implementado utilizando listas simplemente enlazadas) cuyos nodos
476 representan valores de campo eléctrico y magnético. El algoritmo es el
477 descripto por Culler, et al. [CDG93]_.
479 Código fuente disponible en:
480 http://www.fantascienza.net/leonardo/js/dolden_em3d.zip
485 Este programa implementa una heurística para resolver el problema del viajante
486 (*traveling salesman problem*) utilizando árboles binarios balanceados. El
487 algoritmo utilizado es el descripto por Karp [KAR77]_.
490 Código fuente disponible en:
491 http://www.fantascienza.net/leonardo/js/dolden_tsp.zip
496 Este programa genera un conjunto aleatorio de puntos y computa su diagrama de
497 Voronoï, una construcción geométrica que permite construir una partición del
498 plano euclídeo, utilizando el algoritmo descripto por Guibas & Stolfi [GS85]_.
500 Código fuente disponible en: http://codepad.org/xGDCS3KO
504 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
506 Dil_ (escrito en su mayor parte por Aziz Köksal y publicado bajo licencia
507 GPL_) es, lamentablemente, el único programa real hallado que, a pesar de
508 estar incompleto, es lo suficientemente grande, mantenido y estable como para
509 ser incluido en el banco de pruebas. Se trata de un compilador de D_ escrito
510 en D_ y está incompleto porque no puede generar código (falta implementar el
511 análisis semántico y la generación de código), por lo que es principalmente
512 utilizado para generar documentación a partir del código.
514 El programa está compuesto por:
516 * 32.000 líneas de código fuente (aproximadamente).
517 * 86 módulos (o archivos).
518 * 322 diferentes tipos de datos definidos por el usuario, de los cuales 34 son
519 tipos *livianos* (``struct``) y 288 tipos polimórficos (``class``), de los
520 que 260 son subtipos (sub-clases).
522 Puede observarse entonces que a pesar de ser incompleto, es una pieza de
523 software bastante compleja y de dimensión considerable.
525 Además, al interpretar código fuente se hace un uso intensivo de cadenas de
526 texto que en general presentan problemas muy particulares por poder ser
527 objetos extremadamente pequeños y de tamaños poco convencionales (no múltiplos
528 de palabras, por ejemplo). A su vez, el texto interpretado es convertido a una
529 representación interna en forma de árbol (o *árbol de sintaxis abstracta*)
530 modelado por tipos *livianos* y polimórficos que están organizados en arreglos
531 dinámicos contiguos y asociativos (que usan muchos servicios del recolector),
532 y que finalmente son manipulados para obtener y generar la información
533 necesaria, creando y dejando *morir* objetos constantemente (pero no como única
534 forma de procesamiento, como otras pruebas sintetizadas).
536 Por último, a diferencia de muchos otros programas escritos en D_, que dadas
537 algunas de las ineficiencias del recolector invierten mucho trabajo en limitar
538 su uso, este programa no está escrito pensando en dichas limitaciones, por lo
539 que muestra un funcionamiento muy poco sesgado por estas infortunadas
542 Por todas estas razones, Dil_ es el ejemplar que tal vez mejor sirve a la hora
543 de medir de forma realista los resultados obtenidos o los avances realizados.
544 Si bien, como se ha dicho anteriormente, las demás pruebas del banco pueden
545 ser útiles para encontrar problemas muy particulares, está es la que da una
546 lectura más cercana a la realidad del uso de un recolector.
550 Modificaciones propuestas
551 ----------------------------------------------------------------------------
558 ----------------------------------------------------------------------------
564 .. include:: links.rst
566 .. vim: set ts=3 sts=3 sw=3 et tw=78 spelllang=es :