]> git.llucax.com Git - z.facultad/75.00/informe.git/blob - source/solucion.rst
Reorganizar flotantes para que queden mejor distribuidos
[z.facultad/75.00/informe.git] / source / solucion.rst
1
2 .. _solucion:
3
4 Solución adoptada
5 ============================================================================
6
7 Como hemos visto en :ref:`dgc`, la mejora del recolector de basura puede ser
8 abordada desde múltiples flancos, con varias alternativas viables. Por lo
9 tanto, para reducir la cantidad de posibilidades hay que tener en cuenta uno
10 de los principales objetivos de este trabajo: encontrar una solución que tenga
11 una buena probabilidad de ser adoptada por el lenguaje, o alguno de sus
12 compiladores al menos. Para asegurar esto, la solución debe tener un alto
13 grado de aceptación en la comunidad, lo que implica algunos puntos claves:
14
15 * La eficiencia general de la solución no debe ser notablemente peor, en
16   ningún aspecto, que la implementación actual.
17 * Los cambios no deben ser drásticos.
18 * La solución debe atacar de forma efectiva al menos uno de los problemas
19   principales del recolector actual.
20
21 Bajo estos requerimientos, se concluye que probablemente el área más fértil
22 para explorar sea la falta de concurrencia por cumplir todos estos puntos:
23
24 * Si bien hay evidencia en la literatura sobre el incremento del tiempo de
25   ejecución total de ejecución de un programa al usar algoritmos concurrentes,
26   éste no es, en general, muy grande comparativamente.
27 * Existen algoritmos de recolección concurrente que no requieren ningún grado
28   de cooperación por parte del lenguaje o el compilador.
29 * La falta de concurrencia y los largos tiempos de pausa es una de las
30   críticas más frecuentes al recolector actual por parte de la comunidad.
31
32 A pesar de ser la concurrencia la veta principal a explorar en este trabajo,
33 se intenta abordar los demás problemas planteados siempre que sea posible
34 hacerlo sin alejarse demasiado del objetivo principal.
35
36
37 .. highlight:: d
38
39 .. _sol_bench:
40
41 Banco de pruebas
42 ----------------------------------------------------------------------------
43
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.
48
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.
54
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.
60
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.
66
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
71    estándar).
72
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.
81
82 Por lo tanto el banco de pruebas que se conformó como una mezcla de estas tres
83 grandes categorías.
84
85
86 .. _sol_bench_synth:
87
88 Pruebas sintetizadas
89 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
90
91 Este es el juego de programas triviales, escritos con el único objetivo de
92 ejercitar un área particular y acotada del recolector.
93
94
95 ``bigarr``
96 ^^^^^^^^^^
97 Su objetivo es ejercitar la manipulación de arreglos de tamaño considerable
98 que almacenan objetos de tamaño pequeño o mediano. Esta prueba fue hallada__
99 en el grupo de noticias de D_ y escrita por Babele Dunnit y aunque
100 originalmente fue concebido para mostrar un problema con la concatenación de
101 arreglos (como se aprecia en la sentencia ``version(loseMemory)``), ejercita
102 los aspectos más utilizados del del recolector: manipulación de arreglos
103 y petición e memoria. Es una de las pruebas que más estresa al recolector ya
104 que todo el trabajo que realiza el programa es utilizar servicios de éste.
105
106 El código fuente del programa es el siguiente::
107
108    const IT = 300;
109    const N1 = 20_000;
110    const N2 = 40_000;
111
112    class Individual
113    {
114       Individual[20] children;
115    }
116
117    class Population
118    {
119       void grow()
120       {
121          foreach (inout individual; individuals)
122             individual = new Individual;
123       }
124       Individual[N1] individuals;
125    }
126
127    version = loseMemory;
128
129    int main(char[][] args)
130    {
131
132       Population testPop1 = new Population;
133       Population testPop2 = new Population;
134       Individual[N2] indi;
135       for (int i = 0; i < IT; i++) {
136          testPop1.grow();
137          testPop2.grow();
138          version (loseMemory) {
139             indi[] = testPop1.individuals ~ testPop2.individuals;
140          }
141          version (everythingOk) {
142             indi[0 .. N1] = testPop1.individuals;
143             indi[N1 .. N2] = testPop2.individuals;
144          }
145       }
146       return 0;
147    }
148
149 __ http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D&article_id=54084
150
151
152 ``concpu`` y ``conalloc``
153 ^^^^^^^^^^^^^^^^^^^^^^^^^
154 Estos dos programas fueron escritos especialmente para este trabajo con el fin
155 de ejercitar la interacción entre el recolector y un *mutator* con varios
156 hilos. La única diferencia entre ellos es que ``concpu`` lanza hilos que hacen
157 trabajar de forma intensiva el procesador pero que no utilizan servicios del
158 recolector, salvo en el hilo principal, mientras que ``conalloc`` utiliza
159 servicios del recolector en todos los hilos lanzados.
160
161 El objetivo de estos programas es medir el impacto de las pausas del
162 recolector. Se espera medir dos tipos de pausa principales, por un lado el
163 tiempo máximo de pausa real, que puede involucrar a más de un hilo y por otro
164 el tiempo de *stop-the-world*, es decir, el tiempo en que los hilos son
165 efectivamente pausados por el recolector para tomar una *foto* de la pila
166 y registros para agregarlos al *root set*.
167
168 Se espera ``concpu`` sea capaz de explotar cualquier reducción en el tiempo de
169 *stop-the-world*, ya que los hilos solo son interrumpidos por este tipo de
170 pausa. Por otro lado, se espera que ``conalloc`` sea afectado por el tiempo
171 máximo de pausa, que podrían sufrir los hilos incluso cuando el *mundo* sigue
172 su marcha, debido al *lock* global del recolector y que los hilos usan
173 servicios de éste.
174
175 El código de ``concpu`` es el siguiente::
176
177    import tango.core.Thread: Thread;
178    import tango.core.Atomic: Atomic;
179    import tango.io.device.File: File;
180    import tango.util.digest.Sha512: Sha512;
181    import tango.util.Convert: to;
182
183    auto N = 100;
184    auto NT = 2;
185    ubyte[] BYTES;
186    Atomic!(int) running;
187
188    void main(char[][] args)
189    {
190       auto fname = args[0];
191       if (args.length > 3)
192          fname = args[3];
193       if (args.length > 2)
194          NT = to!(int)(args[2]);
195       if (args.length > 1)
196          N = to!(int)(args[1]);
197       N /= NT;
198       running.store(NT);
199       BYTES = cast(ubyte[]) File.get(fname);
200       auto threads = new Thread[NT];
201       foreach(ref thread; threads) {
202          thread = new Thread(&doSha);
203          thread.start();
204       }
205       while (running.load()) {
206          auto a = new void[](BYTES.length / 4);
207          a[] = cast(void[]) BYTES[];
208          Thread.yield();
209       }
210       foreach(thread; threads)
211          thread.join();
212    }
213
214    void doSha()
215    {
216       auto sha = new Sha512;
217       for (size_t i = 0; i < N; i++)
218          sha.update(BYTES);
219       running.decrement();
220    }
221
222 El código de ``conalloc`` es igual excepto por la función ``doSha()``, que es
223 de la siguiente manera::
224
225    void doSha()
226    {
227       for (size_t i = 0; i < N; i++) {
228          auto sha = new Sha512;
229          sha.update(BYTES);
230       }
231       running.decrement();
232    }
233
234
235 ``mcore``
236 ^^^^^^^^^
237 Escrito por David Schima y también hallado__ en el grupo de noticias de D_,
238 este programa pretende mostrar como afecta el *lock* global del recolector
239 en ambientes *multi-core*, incluso cuando a simple vista parecen no utilizarse
240 servicios del recolector::
241
242    import tango.core.Thread;
243
244    void main()
245    {
246       enum { nThreads = 4 };
247       auto threads = new Thread[nThreads];
248       foreach (ref thread; threads) {
249          thread = new Thread(&doAppending);
250          thread.start();
251       }
252       foreach (thread; threads)
253          thread.join();
254    }
255
256    void doAppending()
257    {
258       uint[] arr;
259       for (size_t i = 0; i < 1_000_000; i++)
260          arr ~= i;
261    }
262
263 __ http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D&article_id=103563
264
265 El secreto está en que la concatenación de arreglos utiliza por detrás
266 servicios del recolector, por lo tanto un programa multi-hilo en el cual los
267 hilos (aparentemente) no comparten ningún estado, se puede ver
268 considerablemente afectado por el recolector (siendo este efecto más visible
269 en ambientes *multi-core* por el nivel de sincronización extra que significa
270 a nivel de *hardware*). Cabe destacar que, sin embargo, en Linux_ no es tan
271 notorio.
272
273
274 ``split``
275 ^^^^^^^^^
276 Este programa trivial lee un archivo de texto y genera un arreglo de cadenas
277 de texto resultantes de partir el texto en palabras. Fue escrito por Leonardo
278 Maffi y también hallado__ en el grupo de noticias de D_. Su objetivo era
279 mostrar lo ineficiente que puede ser concatenar datos a un mismo arreglo
280 repetidas veces y ha desembocado en una pequeña optimización que sirvió para
281 paliar el problema de forma razonablemente efectiva [PAN09]_.
282
283 El código es el siguiente::
284
285    import tango.io.device.File: File;
286    import tango.text.Util: delimit;
287    import tango.util.Convert: to;
288
289    int main(char[][] args) {
290       if (args.length < 2)
291          return 1;
292       auto txt = cast(byte[]) File.get(args[1]);
293       auto n = (args.length > 2) ? to!(uint)(args[2]) : 1;
294       if (n < 1)
295          n = 1;
296       while (--n)
297          txt ~= txt;
298       auto words = delimit!(byte)(txt, cast(byte[]) " \t\n\r");
299       return !words.length;
300    }
301
302 __ http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D&article_id=67673
303
304
305 ``rnddata``
306 ^^^^^^^^^^^
307 Este programa fue escrito por Oskar Linde y nuevamente hallado__ en el grupo
308 de noticias. Fue construido para mostrar como el hecho de que el recolector
309 sea conservativo puede hacer que al leer datos binarios hayan muchos *falsos
310 positivos* que mantengan vivas celdas que en realidad ya no deberían ser
311 accesibles desde el *root set* del grafo de conectividad.
312
313 __ http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D&article_id=46407
314
315 El código del programa es el siguiente::
316
317    import tango.math.random.Random;
318
319    const IT = 125; // number of iterations, each creates an object
320    const BYTES = 1_000_000; // ~1MiB per object
321    const N = 50; // ~50MiB of initial objects
322
323    class C
324    {
325       C c; // makes the compiler not set NO_SCAN
326       long[BYTES/long.sizeof] data;
327    }
328
329    void main() {
330       auto rand = new Random();
331       C[] objs;
332             objs.length = N;
333       foreach (ref o; objs) {
334          o = new C;
335          foreach (ref x; o.data)
336             rand(x);
337       }
338       for (int i = 0; i < IT; ++i) {
339          C o = new C;
340          foreach (ref x; o.data)
341             rand(x);
342          // do something with the data...
343       }
344    }
345
346
347 ``sbtree``
348 ^^^^^^^^^^
349 Este programa está basado en la prueba de nombre ``binary-trees`` de `The
350 Computer Language Benchmarks Game`__, una colección de 12 programas escritos
351 en alrededor de 30 lenguajes de programación para comparar su eficiencia
352 (medida en tiempo de ejecución, uso de memoria y cantidad de líneas de
353 código). De este juego de programas se utilizó solo ``binary-trees`` por ser
354 el único destinado a ejercitar el manejo de memoria. El programa sólo manipula
355 árboles binarios, creándolos y recorriéndolos inmediatamente (no realiza
356 ningún trabajo útil). La traducción a D_ fue realizada por Andrey Khropov
357 y fue hallada__ en el grupo de noticias.
358
359 __ http://shootout.alioth.debian.org/
360 __ http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D&article_id=43991
361
362 El código fuente es el siguiente::
363
364    import tango.util.Convert;
365    alias char[] string;
366
367    int main(string[] args)
368    {
369       int N = args.length > 1 ? to!(int)(args[1]) : 1;
370       int minDepth = 4;
371       int maxDepth = (minDepth + 2) > N ? minDepth + 2 : N;
372       int stretchDepth = maxDepth + 1;
373       int check = TreeNode.BottomUpTree(0, stretchDepth).ItemCheck;
374       TreeNode longLivedTree = TreeNode.BottomUpTree(0, maxDepth);
375       for (int depth = minDepth; depth <= maxDepth; depth += 2) {
376          int iterations = 1 << (maxDepth - depth + minDepth);
377          check = 0;
378          for (int i = 1; i <= iterations; i++) {
379             check += TreeNode.BottomUpTree(i, depth).ItemCheck;
380             check += TreeNode.BottomUpTree(-i, depth).ItemCheck;
381          }
382       }
383       return 0;
384    }
385
386    class TreeNode
387    {
388       TreeNode left, right;
389       int item;
390
391       this(int item, TreeNode left = null, TreeNode right = null)
392       {
393          this.item = item;
394          this.left = left;
395          this.right = right;
396       }
397
398       static TreeNode BottomUpTree(int item, int depth)
399       {
400          if (depth > 0)
401             return new TreeNode(item,
402                   BottomUpTree(2 * item - 1, depth - 1),
403                   BottomUpTree(2 * item, depth - 1));
404          return new TreeNode(item);
405       }
406
407       int ItemCheck()
408       {
409          if (left)
410             return item + left.ItemCheck() - right.ItemCheck();
411          return item;
412       }
413    }
414
415
416 .. _sol_bench_small:
417
418 Programas pequeños
419 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
420
421 Todos los pequeños programas utilizados como parte del banco de prueba
422 provienen del `Olden Benchmark`__ [CAR95]_. Estos programas fueron diseñados
423 para probar el lenguaje de programación Olden__; un lenguaje diseñado para
424 paralelizar programas automáticamente en arquitecturas con memoria
425 distribuida. Son programas relativamente pequeños (entre 400 y 1000 líneas de
426 código fuente cada uno) que realizan una tarea secuencial que asigna
427 estructuras de datos dinámicamente. Las estructuras están usualmente
428 organizadas como listas o árboles, y muy raramente como arreglos. Los
429 programas pasan la mayor parte del tiempo alocando datos y el resto usando los
430 datos alocados, por lo que en general están acotados en tiempo por el uso de
431 memoria (y no de procesador).
432
433 __ http://www.irisa.fr/caps/people/truong/M2COct99/Benchmarks/Olden/Welcome.html
434 __ http://www.martincarlisle.com/olden.html
435
436 La traducción a D_ fue realizada por Leonardo Maffi y están basadas a su vez
437 en la traducción de este juego de pruebas a Java_, JOlden__ [CMK01]_. En Java_
438 no se recomienda utilizar este conjunto de pruebas para medir la eficiencia
439 del recolector de basura, dado que se han creado mejores pruebas para este
440 propósito, como DaCapo__ [BLA06]_, sin embargo, dada la falta de programas
441 disponibles en general, y de un conjunto de pruebas especialmente diseñado
442 para evaluar el recolector de basura en D_, se decide utilizarlas en este
443 trabajo de todos modos. Sin embargo sus resultados deben ser interpretados con
444 una pizca de sal por lo mencionado anteriormente.
445
446 __ http://www-ali.cs.umass.edu/DaCapo/benchmarks.html
447 __ http://www.dacapobench.org/
448
449 En general (salvo para el programa ``voronoï``) está disponible el código
450 fuente portado a D_, Java_ y Python_, e incluso varias versiones con distintas
451 optimizaciones para reducir el consumo de tiempo y memoria. Además provee
452 comparaciones de tiempo entre todas ellas. Los programas utilizados en este
453 banco de pruebas son la versión traducida más literalmente de Java_ a D_, ya
454 que hace un uso más intensivo del recolector que las otras versiones.
455
456 A continuación se da una pequeña descripción de cada uno de los 5 programas
457 traducidos y los enlaces en donde encontrar el código fuente (y las
458 comparaciones de tiempos estar disponibles).
459
460
461 ``bh``
462 ^^^^^^
463 Este programa computa las interacciones gravitatorias entre un número
464 :math:`N` de cuerpos en tiempo :math:`O(N log N)` y está basado en árboles
465 heterogéneos de 8 ramas, según el algoritmo descripto por Barnes & Hut
466 [BH86]_.
467
468 Código fuente disponible en:
469 http://www.fantascienza.net/leonardo/js/dolden_bh.zip
470
471
472 ``bisort``
473 ^^^^^^^^^^
474 Este programa ordena :math:`N` números, donde :math:`N` es una potencia de 2,
475 usando un ordenamiento *Bitonic* adaptativo, un algoritmo paralelo óptimo para
476 computadoras con memoria compartida, según describen Bilardi & Nicolau
477 [BN98]_. Utiliza árboles binarios como principal estructuras de datos.
478
479 Código fuente disponible en:
480 http://www.fantascienza.net/leonardo/js/dolden_bisort.zip
481
482
483 ``em3d``
484 ^^^^^^^^
485 Este programa modela la propagación de ondas electromagnéticas a través de
486 objetos en 3 dimensiones. Realiza un cálculo simple sobre un grafo irregular
487 bipartito (implementado utilizando listas simplemente enlazadas) cuyos nodos
488 representan valores de campo eléctrico y magnético. El algoritmo es el
489 descripto por Culler, et al. [CDG93]_.
490
491 Código fuente disponible en:
492 http://www.fantascienza.net/leonardo/js/dolden_em3d.zip
493
494
495 ``tsp``
496 ^^^^^^^
497 Este programa implementa una heurística para resolver el problema del viajante
498 (*traveling salesman problem*) utilizando árboles binarios balanceados. El
499 algoritmo utilizado es el descripto por Karp [KAR77]_.
500
501
502 Código fuente disponible en:
503 http://www.fantascienza.net/leonardo/js/dolden_tsp.zip
504
505
506 ``voronoï``
507 ^^^^^^^^^^^
508 Este programa genera un conjunto aleatorio de puntos y computa su diagrama de
509 Voronoï, una construcción geométrica que permite construir una partición del
510 plano euclídeo, utilizando el algoritmo descripto por Guibas & Stolfi [GS85]_.
511
512 Código fuente disponible en: http://codepad.org/xGDCS3KO
513
514
515 .. _sol_bench_real:
516
517 Programas *reales*
518 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
519
520 Dil_ (escrito en su mayor parte por Aziz Köksal y publicado bajo licencia
521 GPL_) es, lamentablemente, el único programa real hallado que, a pesar de
522 estar incompleto, es lo suficientemente grande, mantenido y estable como para
523 ser incluido en el banco de pruebas. Se trata de un compilador de D_ escrito
524 en D_ y está incompleto porque no puede generar código (falta implementar el
525 análisis semántico y la generación de código), por lo que es principalmente
526 utilizado para generar documentación a partir del código.
527
528 El programa está compuesto por:
529
530 * 32.000 líneas de código fuente (aproximadamente).
531 * 86 módulos (o archivos).
532 * 322 diferentes tipos de datos definidos por el usuario, de los cuales 34 son
533   tipos *livianos* (``struct``) y 288 tipos polimórficos (``class``), de los
534   que 260 son subtipos (sub-clases).
535
536 Puede observarse entonces que a pesar de ser incompleto, es una pieza de
537 software bastante compleja y de dimensión considerable.
538
539 Además, al interpretar código fuente se hace un uso intensivo de cadenas de
540 texto que en general presentan problemas muy particulares por poder ser
541 objetos extremadamente pequeños y de tamaños poco convencionales (no múltiplos
542 de palabras, por ejemplo). A su vez, el texto interpretado es convertido a una
543 representación interna en forma de árbol (o *árbol de sintaxis abstracta*)
544 modelado por tipos *livianos* y polimórficos que están organizados en arreglos
545 dinámicos contiguos y asociativos (que usan muchos servicios del recolector),
546 y que finalmente son manipulados para obtener y generar la información
547 necesaria, creando y dejando *morir* objetos constantemente (pero no como única
548 forma de procesamiento, como otras pruebas sintetizadas).
549
550 Por último, a diferencia de muchos otros programas escritos en D_, que dadas
551 algunas de las ineficiencias del recolector invierten mucho trabajo en limitar
552 su uso, este programa no está escrito pensando en dichas limitaciones, por lo
553 que muestra un funcionamiento muy poco sesgado por estas infortunadas
554 circunstancias.
555
556 Por todas estas razones, Dil_ es el ejemplar que tal vez mejor sirve a la hora
557 de medir de forma realista los resultados obtenidos o los avances realizados.
558 Si bien, como se ha dicho anteriormente, las demás pruebas del banco pueden
559 ser útiles para encontrar problemas muy particulares, está es la que da una
560 lectura más cercana a la realidad del uso de un recolector.
561
562
563 .. highlight:: pcode
564
565 .. _sol_mod:
566
567 Modificaciones propuestas
568 ----------------------------------------------------------------------------
569
570 Se decide realizar todas las modificaciones al recolector actual de forma
571 progresiva e incremental, partiendo como base del recolector de la versión
572 0.99.9 de Tango_.  Las razones que motivan esta decisión son varias; por un
573 lado es lo más apropiado dados los requerimientos claves mencionados al
574 principio de este capítulo. Por ejemplo, al hacer cambios incrementales es más
575 fácil comprobar que la eficiencia no se aleja mucho del actual con cada
576 modificación y una modificación gradual impone menos resistencia a la
577 aceptación del nuevo recolector.
578
579 Además la construcción de un recolector de cero es una tarea difícil
580 considerando que un error en el recolector es extremadamente complejo de
581 rastrear, dado que en general el error se detecta en el *mutator* y en una
582 instancia muy posterior al origen real del error. Esto ha sido comprobado de
583 forma práctica, dado que, a modo de ejercicio para interiorizarse en el
584 funcionamiento del *runtime* de D_, primero se ha construido desde cero una
585 implementación de un recolector *naïve*, resultando muy difícil su depuración
586 por las razones mencionadas. Por el contrario, comenzar con un recolector en
587 funcionamiento como base hace más sencillo tanto probar cada pequeña
588 modificación para asegurar que no introduce fallos, como encontrar y reparar
589 los fallos cuando estos se producen, ya que el código incorrecto introducido
590 está bien aislado e identificado.
591
592 A continuación se hace un recorrido sobre cada una de las mejoras propuestas,
593 y en los casos en los que la mejora propone un cambio algorítmico, se analiza
594 la corrección del algoritmo resultante, partiendo de la base de que el
595 algoritmo tomado como punto de partida es un marcado y barrido que utiliza la
596 abstracción tricolor para hacer la fase de marcado de forma iterativa (ver
597 :ref:`gc_mark_sweep` y :ref:`gc_intro_tricolor`), cuya corrección ya está
598 probada en la literatura preexistente.
599
600
601 .. _sol_config:
602
603 Configurabilidad
604 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
605
606 Una de las primeras mejoras propuestas es la posibilidad de configurar el
607 recolector de forma más sencilla. El requerimiento mínimo es la posibilidad de
608 configurar el recolector sin necesidad de recompilarlo. La complejidad de esto
609 surge de que el recolector debe ser transparente para el programa del usuario.
610
611 Configurar el recolector en tiempo de compilación del programa del usuario
612 probablemente requeriría modificar el compilador, y además, si bien es una
613 mejora sustancial a la configuración en tiempo de compilación del recolector,
614 no termina de ser completamente conveniente para realizar pruebas reiteradas
615 con un mismo programa para encontrar los mejores valores de configuración para
616 ese programa en particular.
617
618 Por otro lado, permitir configurar el recolector en tiempo de ejecución, una
619 vez que su estructura interna ya fue definida y creada, puede ser no solo
620 tedioso y complejo, además ineficiente, por lo tanto esta opción también se
621 descarta.
622
623 Finalmente, lo que parece ser más apropiado para un recolector, es permitir la
624 configuración en tiempo de inicialización. Es decir, configurar el recolectar
625 sin necesidad de recompilar ni el programa del usuario ni el recolector, pero
626 antes de que el programa del usuario inicie, de manera que una vez iniciado el
627 recolector con ciertos parámetros, éstos no cambien nunca más en durante la
628 vida del programa.
629
630 Este esquema provee la mejor relación entre configurabilidad, conveniencia,
631 eficiencia y simplicidad. Una posibilidad para lograr esto es utilizar
632 parámetros de línea de comandos, sin embargo no parece ni sencillo (proveer
633 una forma de leer los parámetros de línea de comandos requiere cambios en el
634 *runtime*) ni apropiado (el recolector debería ser lo más transparente posible
635 para el programa del usuario).
636
637 Otra posibilidad es utilizar variables de entorno, que parece ser la opción
638 más sencilla y apropiada. Sencilla porque las variables de entorno pueden ser
639 leídas directamente al inicializar el recolector sin necesidad de cooperación
640 alguna del *runtime*, a través de :manpage:`getenv(3)`. Apropiada porque, si
641 bien el problema de invasión del programa del usuario también existe, es una
642 práctica más frecuente y aceptada la configuración de módulos internos
643 o bibliotecas compartidas a través de variables de entorno.
644
645 Por último, antes de comenzar a usar este esquema de configuración, se
646 verifica que tomar ciertas decisiones en tiempo de ejecución no impacten en la
647 eficiencia del recolector. Para esto se convierten algunas opciones que antes
648 eran solo seleccionables en tiempo de compilación del recolector para que
649 puedan ser seleccionables en tiempo de inicialización y se comprueba que no
650 hay una penalización apreciable.
651
652
653 .. _sol_config_spec:
654
655 Especificación de opciones
656 ^^^^^^^^^^^^^^^^^^^^^^^^^^
657 Para especificar opciones de configuración, hay que hacerlo a través de la
658 variable de entorno de nombre :envvar:`D_GC_OPTS`. El valor de esa variable es
659 interpretado de la siguiente manera (en formato similar a :term:`BNF`):
660
661 .. productionlist::
662    D_GC_OPTS: `option` ( ':' `option` )* <lista de opciones>
663    option: `name` [ '=' `value` ]
664    name: `namec` `namec`*                <nombre de la opción>
665    value: `valuec`*                      <valor de la opción>
666    namec: `valuec` - '='
667    valuec: [0x01-0xFF] - ':'             <cualquier char salvo '\0' y ':'>
668
669 Es decir, se compone de una lista de opciones separadas por **:**. Cada opción
670 se especifica con un nombre, opcionalmente seguido por un valor (separados por
671 **=**).
672
673 El valor de una opción puede ser un texto arbitrario (exceptuando los
674 caracteres ``'\0'`` y ``':'`` y de longitud máxima 255), pero cada opción lo
675 interpreta de forma particular. Como caso general, hay opciones booleanas, que
676 toman como valor verdadero un cualquier número distinto de 0 (o si el valor es
677 vació, es decir, solo se indica el nombre de la opción), y como valor falso
678 cualquier otro texto.
679
680 A continuación se listan las opciones reconocidas por el recolector (indicando
681 el formato del valor de la opción de tener uno especial):
682
683 ``mem_stomp``
684    Esta es una opción (booleana) disponible en el recolector original, pero
685    que se cambia para que sea configurable en tiempo de inicialización
686    (estando desactivada por omisión). Activa la opción ``MEMSTOMP`` descripta
687    en :ref:`dgc_debug`.
688
689 ``sentinel``
690    Esta opción es también booleana (desactivada por omisión), está disponible
691    en el recolector original, y se la cambia para sea configurable en tiempo
692    de inicialización. Activa la opción ``SENTINEL`` descripta en
693    :ref:`dgc_debug`.
694
695 ``pre_alloc``
696    Esta opción permite crear una cierta cantidad de *pools* de un tamaño
697    determinado previo a que inicie el programa. Si se especifica solo un
698    número, se crea un *pool* con ese tamaño en MiB.  Si, en cambio, se
699    especifica una cadena del tipo ``3x1``, el primer número indica la cantidad
700    de *pools* y el segundo el tamaño en MiB de cada uno (3 *pools* de 1MiB en
701    este caso). Ver :ref:`sol_pre_alloc` para más detalles sobre la utilidad de
702    esta opción.
703
704 ``min_free``
705    El valor de esta opción indica el porcentaje mínimo porcentaje del *heap*
706    que debe quedar libre luego de una recolección. Siendo un porcentaje, solo
707    se aceptan valores entre 0 y 100, siendo su valor por omisión 5. Ver
708    :ref:`sol_ocup` para más detalles sobre su propósito.
709
710 ``malloc_stats_file``
711    Esta opción sirve para especificar un archivo en el cual escribir un
712    reporte de todas la operaciones de pedido de memoria realizadas por el
713    programa (durante su tiempo de vida).  Ver :ref:`sol_stats` para más
714    detalles sobre la información provista y el formato del reporte.
715
716 ``collect_stats_file``
717    Esta opción sirve para especificar un archivo en el cual escribir un
718    reporte de todas las recolecciones hechas durante el tiempo de vida del
719    programa.  Ver :ref:`sol_stats` para más detalles sobre la información
720    provista y el formato del reporte.
721
722 ``conservative``
723    Esta opción booleana permite desactivar el escaneo preciso del *heap*,
724    forzando al recolector a ser completamente conservativo (excepto por los
725    bloques con el atributo ``NO_SCAN`` que siguen sin ser escaneados). Ver
726    :ref:`sol_precise` para más detalles sobre la existencia de esta opción.
727
728 ``fork``
729    Esta opción booleana (activada por omisión) permite seleccionar si el
730    recolector debe correr la fase de marcado en paralelo o no (es decir, si el
731    recolector corre de forma concurrente con el *mutator*).  Para más detalles
732    ver :ref:`sol_fork`.
733
734 ``eager_alloc``
735    Esta opción booleana (activada por omisión), sólo puede estar activa si
736    ``fork`` también está activa y sirve para indicar al recolector que reserve
737    un nuevo *pool* de memoria cuando una petición no puede ser satisfecha,
738    justo antes de lanzar la recolección concurrente. Ver
739    :ref:`sol_eager_alloc` para más detalles sobre el propósito de esta opción.
740
741 ``early_collect``
742    Esta opción booleana (desactivada por omisión), también sólo puede estar
743    activa si ``fork`` está activa y sirve para indicar al recolector que lance
744    una recolección (concurrente) antes de que la memoria libre se termine (la
745    recolección temprana será disparada cuando el porcentaje de memoria libre
746    sea menor a ``min_free``). Ver :ref:`sol_early_collect` para más detalles
747    sobre el propósito de esta opción.
748
749 Cualquier opción o valor no reconocido es ignorado por el recolector. Se
750 utilizan los valores por omisión de las opciones que no fueron especificadas,
751 o cuyos valores no pudieron ser interpretados correctamente.
752
753 Para cambiar la configuración del recolector se puede invocar el programa de
754 la siguiente manera (usando un intérprete de comandos del tipo *bourne
755 shell*):
756
757 .. code-block:: none
758
759    D_GC_OPTS=conservative:eager_alloc=0:early_collect=1:pre_alloc=2x5 ./programa
760
761 En este ejemplo, se activan las opciones ``conservative`` y ``early_collect``,
762 se desactiva ``eager_alloc`` y se crean 2 *pools* de 5MiB cada uno al
763 inicializar el recolector.
764
765
766 Reestructuración y cambios menores
767 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
768
769 Si bien se decide no comenzar una implementación desde cero, se ha mostrado
770 (ver :ref:`dgc_bad_code`) que la implementación actual es lo suficientemente
771 desprolija como para complicar su modificación. Es por esto que se hacen
772 algunas reestructuraciones básicas del código, reescribiendo o saneando de
773 forma incremental todas aquellas partes que complican su evolución.
774
775 Además de las modificaciones puramente estéticas (aunque no por eso menos
776 valuables, ya que la legibilidad y simplicidad del código son un factor
777 fundamental a la hora de ser mantenido o extendido), se hacen otras pequeñas
778 mejoras, que se detallan a continuación.
779
780 Remoción de memoria *no-encomendada*
781 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
782 Se elimina la distinción entre memoria *encomendada* y *no-encomendada* (ver
783 :ref:`dgc_committed`), pasando a estar *encomendada* toda la memoria
784 administrada por el recolector.
785
786 Si bien a nivel de eficiencia este cambio no tuvo impacto alguno (cuando en un
787 principio se especuló con que podría dar alguna ganancia en este sentido), se
788 elimina el concepto de memoria *encomendada* para quitar complejidad al
789 código.
790
791 Esta mejora no afecta a la corrección del algoritmo, ya que a nivel lógico el
792 recolector solo ve la memoria *encomendada*.
793
794 .. _sol_minor_findsize:
795
796 Caché de ``Pool.findSize()``
797 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
798 Se crea un caché de tamaño de bloque para el método ``findSize()`` de un
799 *pool*. Esto acelera considerablemente las operaciones que necesitan pedir el
800 tamaño de un bloque reiteradamente, por ejemplo, al añadir nuevos elementos
801 a un arreglo dinámico. En esencia es una extensión a una de las optimizaciones
802 propuestas por Vladimir Panteleev [PAN09]_, que propone un caché global para
803 todo el recolector en vez de uno por *pool*.
804
805 Esta mejora tampoco afecta a la corrección del algoritmo, ya que nuevamente no
806 afecta su comportamiento a nivel lógico, solo cambia detalles en la
807 implementación de forma transparentes para el algoritmo de recolección.
808
809 Optimizaciones sobre ``findPool()``
810 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
811 Al analizar los principales cuellos de botella del recolector, es notoria la
812 cantidad de tiempo que pasa ejecutando la función ``findPool()``, que dado un
813 puntero devuelve el *pool* de memoria al cual pertenece. Es por esto que se
814 minimiza el uso de esta función. Además, dado que los *pools* de memoria están
815 ordenados por el puntero de comienzo del bloque de memoria manejado por el
816 *pool*, se cambia la búsqueda (originalmente lineal) por una búsqueda binaria.
817 Finalmente, dado que la lista de libre está construida almacenando el puntero
818 al siguiente en las mismas celdas que componen la lista, se almacena también
819 el puntero al *pool* al que dicha celda pertenece (dado que la celda más
820 pequeña es de 16 bytes, podemos garantizar que caben dos punteros, incluso
821 para arquitecturas de 64 bits). De esta manera no es necesario usar
822 ``findPool()`` al quitar una celda de la lista de libres.
823
824 Una vez más, la mejora no afecta la corrección del código.
825
826 .. _sol_pre_alloc:
827
828 Pre-asignación de memoria
829 ^^^^^^^^^^^^^^^^^^^^^^^^^
830 Esta opción permite crear una cierta cantidad de *pools* de un tamaño
831 determinado previo a que inicie el programa. Normalmente el recolector no
832 reserva memoria hasta que el programa lo pida. Esto puede llegar a evitar
833 que un programa haga muchas recolecciones al comenzar, hasta que haya
834 cargado su conjunto de datos de trabajo.
835
836 Se han analizado varios valores por omisión pero ninguno es consistentemente
837 mejor que comenzar sin memoria asignada, por lo tanto no se cambia el
838 comportamiento original, pero se agrega una opción (ver ``pre_alloc`` en
839 :ref:`sol_config_spec`) para que el usuario pueda experimentar con cada
840 programa en particular si esta opción es beneficiosa.
841
842 Esta opción tampoco cambia la corrección del algoritmo de recolección, solo
843 sus condiciones iniciales.
844
845 .. _sol_ocup:
846
847 Mejora del factor de ocupación del *heap*
848 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
849 El factor de ocupación del *heap* debe ser apropiado por dos razones. Por un
850 lado, si el *heap* está demasiado ocupado todo el tiempo, serán necesarias
851 muchas recolecciones que, aunque pequeñas dado que la memoria utilizada es
852 poca, puede llegar a ser extremadamente ineficiente en casos patológicos (ver
853 :ref:`dgc_bad_ocup`). Por otro lado, si el tamaño del *heap* es extremadamente
854 grande (en comparación con el tamaño real del grupo de trabajo del programa),
855 se harán pocas recolecciones pero cada una es muy costosa, porque el algoritmo
856 de marcado y barrido es :math:`O(\lvert Heap \rvert)` (ver
857 :ref:`gc_mark_sweep`). Además la afinidad del caché va a ser extremadamente
858 pobre.
859
860 Para mantener el factor de ocupación dentro de límites razonables, se agrega
861 la opción ``min_free`` (ver :ref:`sol_config_spec`). Esta opción indica el
862 recolector cual debe ser el porcentaje mínimo del *heap* que debe quedar libre
863 luego de una recolección. En caso de no cumplirse, se pide más memoria al
864 sistema operativo para cumplir este requerimiento. Además, luego de cada
865 recolección se verifica que el tamaño del *heap* no sea mayor a ``min_free``,
866 para evitar que el *heap* crezca de forma descontrolada. Si es mayor
867 a ``min_free`` se intenta minimizar el uso de memoria liberando *pools* que
868 estén completamente desocupados, mientras que el factor de ocupación siga
869 siendo mayor a ``min_free``. Si liberar un *pool* implica pasar ese límite, no
870 se libera y se pasa a analizar el siguiente y así sucesivamente.
871
872 Esta modificación no afecta a la corrección del algoritmo, ya que no lo afecta
873 directamente.
874
875 Modificaciones descartadas
876 ^^^^^^^^^^^^^^^^^^^^^^^^^^
877 Se realizan varias otras modificaciones, con la esperanza de mejorar la
878 eficiencia del recolector, pero que, al contrario de lo esperado, empeoran la
879 eficiencia o la mejoran de forma muy marginal en comparación con la
880 complejidad agregada.
881
882 Probablemente el caso más significativo, y por tanto el único que vale la pena
883 mencionar, es la conversión de marcado iterativo a marcado recursivo y luego
884 a un esquema híbrido. Como se describe en :ref:`dgc_bad`, el marcado iterativo
885 tiene sus ventajas, pero tiene desventajas también. Al convertirlo a puramente
886 recursivo, es impracticable por resultar en errores de desbordamiento de pila.
887
888 Por lo tanto se prueba con un esquema híbrido, poniendo un límite a la
889 recursividad, volviendo al algoritmo iterativo cuando se alcanza este límite.
890
891 La implementación del algoritmo híbrido consiste en los siguientes cambios
892 sobre el algoritmo original (ver :ref:`dgc_algo_mark`)::
893
894    function mark_phase() is
895       global more_to_scan = false
896       global depth = 0                                // Agregado
897       stop_the_world()
898       clear_mark_scan_bits()
899       mark_free_lists()
900       mark_static_data()
901       push_registers_into_stack()
902       thread_self.stack.end = get_stack_top()
903       mark_stacks()
904       pop_registers_from_stack()
905       mark_user_roots()
906       mark_heap()
907       start_the_world()
908
909    function mark_range(begin, end) is
910       pointer = begin
911       global depth++                                  // Agregado
912       while pointer < end
913          [pool, page, block] = find_block(pointer)
914          if block is not null and block.mark is false
915             block.mark = true
916             if block.noscan is false
917                block.scan = true
918                if (global depth > MAX_DEPTH)          //
919                   more_to_scan = true                 //
920                else                                   // Agregado
921                   foreach ptr in block.words          //
922                      mark(ptr)                        //
923       global depth--                                  //
924
925 Al analizar los resultados de de esta modificación, se observa una mejoría muy
926 level, para valores de ``MAX_DEPTH`` mayores a cero (en algunos casos bastante
927 mayores) y en general para ``MAX_DEPTH`` cero (es decir, usando el algoritmo
928 de forma completamente iterativa) los resultados son peores, dado que se paga
929 el trabajo extra sin ganancia alguna. En la figura :vref:`fig:sol-mark-rec` se
930 puede ver, por ejemplo, el tiempo total de ejecución de Dil_ al generar la
931 documentación completa del código de Tango_, según varía el valor de
932 ``MAX_DEPTH``.
933
934 .. flt:: fig:sol-mark-rec
935
936    Análisis de tiempo total de ejecución en función del valor de
937    ``MAX_DEPTH``
938
939    Tiempo total de ejecución de Dil_ al generar la documentación completa del
940    código de Tango_ en función del valor de ``MAX_DEPTH``. El rombo no
941    pertenece a ningún nivel de recursividad, representa el tiempo de ejecución
942    del algoritmo original (puramente iterativo).
943
944    .. image:: sol-mark-rec-dil.pdf
945
946
947 Dado que aumentar el nivel máximo de recursividad significa un uso mayor del
948 *stack*, y que esto puede impactar en el usuario (si el usuario tuviera un
949 programa que esté al borde de consumir todo el *stack*, el recolector podría
950 hacer fallar al programa de una forma inesperada para el usuario, problema que
951 sería muy difícil de depurar para éste), y que los resultados obtenidos no son
952 rotundamente superiores a los resultados sin esta modificación, se opta por no
953 incluir este cambio. Tampoco vale la pena incluirlo como una opción con valor
954 por omisión 0 porque, como se ha dicho, para este caso el resultado es incluso
955 peor que sin la modificación.
956
957 Esta modificación mantiene la corrección del recolector dado que tampoco
958 modifica el algoritmo sino su implementación. Además ambos casos extremos son
959 correctos (si ``MAX_DEPTH`` es 0, el algoritmo es puramente iterativo y si
960 pudiera ser infinito resultaría en el algoritmo puramente recursivo).
961
962
963 .. _sol_stats:
964
965 Recolección de estadísticas
966 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
967
968 Un requerimiento importante, tanto para evaluar los resultados de este trabajo
969 como para analizar el comportamiento de los programas estudiados, es la
970 recolección de estadísticas. Hay muchos aspectos que pueden ser analizados
971 a la hora de evaluar un recolector, y es por esto que se busca que la
972 recolección de datos sea lo más completa posible.
973
974 Con este objetivo, se decide recolectar datos sobre lo que, probablemente,
975 sean las operaciones más importantes del recolector: asignación de memoria
976 y recolección.
977
978 Todos los datos recolectados son almacenados en archivos que se especifican
979 a través de opciones del recolector (ver :ref:`sol_config_spec`). Los archivos
980 especificados debe poder ser escritos (y creados de ser necesario) por el
981 recolector (de otra forma se ignora la opción). El conjunto de datos
982 recolectados son almacenados en formato :term:`CSV` en el archivo, comenzando
983 con una cabecera que indica el significado de cada columna.
984
985 Los datos recolectados tienen en general 4 tipos de valores diferentes:
986
987 Tiempo
988    Se guarda en segundos como número de punto flotante (por ejemplo ``0.12``).
989
990 Puntero
991    Se guarda en forma hexadecimal (por ejemplo ``0xa1b2c3d4``).
992
993 Tamaño
994    Se guarda como un número decimal, expresado en bytes (por ejemplo ``32``).
995
996 Indicador
997    Se guarda como el número ``0`` si es falso o ``1`` si es verdadero.
998
999 Esta modificación mantiene la corrección del recolector dado que no hay cambio
1000 algorítmico alguno.
1001
1002 Asignación de memoria
1003 ^^^^^^^^^^^^^^^^^^^^^
1004 La recolección de datos sobre asignación de memoria se activa asignando un
1005 nombre de archivo a la opción ``malloc_stats_file``. Por cada asignación de
1006 memoria pedida por el programa (es decir, por cada llamada a la función
1007 ``gc_malloc()``) se guarda una fila con los siguientes datos:
1008
1009 1. Cantidad de segundos que pasaron desde que empezó el programa (*timestamp*).
1010 2. Tiempo total que tomó la asignación de memoria.
1011 3. Valor del puntero devuelto por la asignación.
1012 4. Tamaño de la memoria pedida por el programa.
1013 5. Si esta petición de memoria disparó una recolección o no.
1014 6. Si debe ejecutarse un *finalizador* sobre el objeto (almacenado en la
1015    memoria pedida) cuando ésta no sea más alcanzable (cuando sea barrido).
1016 7. Si objeto carece de punteros (es decir, no debe ser escaneada).
1017 8. Si objeto no debe ser movido por el recolector.
1018 9. Puntero a la información sobre la ubicación de los punteros del objeto.
1019 10. Tamaño del tipo del objeto.
1020 11. Primera palabra con los bits que indican que palabras del tipo deben ser
1021     escaneados punteros y cuales no (en hexadecimal).
1022 12. Primera palabra con los bits que indican que palabras del tipo son
1023     punteros garantizados (en hexadecimal).
1024
1025 Como puede apreciarse, la mayor parte de esta información sirve más para
1026 analizar el programa que el recolector. Probablemente solo el punto 2 sea de
1027 interés para analizar como se comporta el recolector.
1028
1029 El punto 8 es completamente inútil, ya que el compilador nunca provee esta
1030 información, pero se la deja por si en algún momento comienza a hacerlo. Los
1031 puntos 9 a 12 provee información sobre el tipo del objeto almacenado, útil
1032 para un marcado preciso (ver :ref:`sol_precise`).
1033
1034 El punto 6 indica, indirectamente, cuales de los objetos asignados son
1035 *pesados*, ya que éstos son los únicos que pueden tener un *finalizador*.
1036 Además, a través de los puntos 4 y 10 es posible inferir si lo que va
1037 almacenarse es un objeto solo o un arreglo de objetos.
1038
1039 Recolección de basura
1040 ^^^^^^^^^^^^^^^^^^^^^
1041 Los datos sobre las recolecciones realizadas se guardan al asignar un nombre
1042 de archivo a la opción ``collect_stats_file``. Cada vez que se dispara una
1043 recolección [#solcollect]_ (es decir, cada vez que se llama a la función
1044 ``fullcollect()``) se guarda una fila con los siguientes datos:
1045
1046 1. Cantidad de segundos que pasaron desde que empezó el programa (*timestamp*).
1047 2. Tiempo total que tomó la asignación de memoria que disparó la recolección.
1048 3. Tiempo total que tomó la recolección.
1049 4. Tiempo total que deben pausarse todos los hilos (tiempo de
1050    *stop-the-world*).
1051 5. Cantidad de memoria usada antes de la recolección.
1052 6. Cantidad de memoria libre antes de la recolección.
1053 7. Cantidad de memoria desperdiciada antes de la recolección.
1054 8. Cantidad de memoria utilizada por el mismo recolector antes de la
1055    recolección (para sus estructuras internas).
1056 9. Cantidad de memoria usada después de la recolección.
1057 10. Cantidad de memoria libre después de la recolección.
1058 11. Cantidad de memoria desperdiciada [#solwaste]_ después de la recolección.
1059 12. Cantidad de memoria utilizada por el mismo recolector después de la
1060     recolección.
1061
1062 Si bien el punto 4 parece ser el más importante para un programa que necesita
1063 baja latencia, dado el *lock* global del recolector, el punto 2 es
1064 probablemente el valor más significativo en este aspecto, dado que, a menos
1065 que el programa en cuestión utilice muy poco el recolector en distintos hilos,
1066 los hilos se verán pausados de todas formas cuando necesiten utilizar el
1067 recolector.
1068
1069 .. [#solcollect] Esto es en el sentido más amplio posible. Por ejemplo, cuando
1070    se utiliza marcado concurrente (ver :ref:`sol_fork`), se guarda esta
1071    información incluso si ya hay una recolección activa, pero el tiempo de
1072    pausa de los hilos será -1 para indicar que en realidad nunca fueron
1073    pausados.
1074
1075 .. [#solwaste] Memoria *desperdiciada* se refiere a memoria que directamente
1076    no puede utilizarse debido a la fragmentación. Si por ejemplo, se piden 65
1077    bytes de memoria, dada la organización del *heap* en bloques (ver
1078    :ref:`dgc_org`), el recolector asignará un bloque de 128 bytes, por lo
1079    tanto 63 bytes quedarán desperdiciados.
1080
1081
1082 .. _sol_precise:
1083
1084 Marcado preciso
1085 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1086
1087 Para agregar el soporte de marcado preciso se aprovecha el trabajo realizado
1088 por Vincent Lang (ver :ref:`dgc_via_art`) [DBZ3463]_, dado que se basa en `D
1089 1.0`_ y Tango_, al igual que este trabajo. Dado el objetivo y entorno común,
1090 se abre la posibilidad de adaptar sus cambios a este trabajo, utilizando una
1091 versión modificada de DMD_ (dado que los cambios aún no son integrados al
1092 compilador oficial).
1093
1094 .. TODO: Apéndice con parches a DMD y Tango?
1095
1096 Información de tipos provista por el compilador
1097 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1098 Con éstas modificaciones, el compilador en cada asignación le pasa al
1099 recolector información sobre los punteros del tipo para el cual se pide la
1100 memoria. Esta información se pasa como un puntero a un arreglo de palabras con
1101 la estructura mostrada en la figura :vref:`fig:sol-ptrmap` y que se describe
1102 a continuación.
1103
1104 .. flt:: fig:sol-ptrmap
1105    :type: table
1106
1107    Estructura de la información de tipos provista por el compilador
1108
1109    .. aafig::
1110       :scale: 110
1111
1112       /----- ptrmap
1113       |
1114       V
1115       +-------------+----------------------------+----------------------------+
1116       | "Tamaño en" |    "Bits indicando si la"  |      "Bits indicando si"   |
1117       | "cantidad"  |  "palabra en una posición" |      "la palabra en una"   |
1118       |    "de"     |    "debe escanearse como"  |          "posición es"     |
1119       | "palabras"  |     "si fuera un puntero"  |          "un puntero"      |
1120       +-------------+----------------------------+----------------------------+
1121
1122       |             |                            |                            |
1123       +----- 1 -----+------- ceil(N/BPW) --------+------- ceil(N/BPW) --------+
1124       |             |                            |                            |
1125
1126 * La primera palabra indica el tamaño, en **cantidad de palabras**, del tipo
1127   para el cual se pide la memoria (:math:`N`).
1128 * Las siguientes :math:`ceil(\frac{N}{BPW})` palabras indican,
1129   como un conjunto de bits, qué palabras deben ser escaneadas por el
1130   recolector como si fueran punteros (donde :math:`BPW` indica la cantidad de
1131   bits por palabra, por ejemplo 32 para x86).
1132 * Las siguientes :math:`ceil(\frac{N}{BPW})` palabras son otro conjunto de
1133   bits indicando qué palabras son realmente punteros.
1134
1135 Los conjuntos de bits guardan la información sobre la primera palabra en el
1136 bit menos significativo. Dada la complejidad de la representación, se ilustra
1137 con un ejemplo. Dada la estructura:
1138
1139 .. code-block:: d
1140
1141    union U {
1142       ubyte ub;
1143       void* ptr;
1144    }
1145
1146    struct S
1147    {
1148       void* begin1;                        // 1 word
1149       byte[size_t.sizeof * 14 + 1] bytes;  // 15 words
1150       // el compilador agrega bytes de "padding" para alinear
1151       void* middle;                        // 1 word
1152       size_t[14] ints;                     // 14 words
1153       void* end1;                          // 1 words
1154       // hasta acá se almacenan los bits en la primera palabra
1155       void* begin2;                        // 1 words
1156       int i;                               // 1 word
1157       U u;                                 // 1 word
1158       S* s;                                // 1 word
1159    }
1160
1161 El compilador genera la estructura que se muestra en la figura
1162 :vref:`fig:sol-ptrmap-example` (asumiendo una arquitectura de 32 bits). Como
1163 puede apreciarse, el miembro ``u``, al ser una unión entre un puntero y un
1164 dato común, el compilador no puede asegurar que lo que se guarda en esa
1165 palabra sea realmente un puntero, pero indica que debe ser escaneado. El
1166 recolector debe debe ser conservativo en este caso, y escanear esa palabra
1167 como si fuera un puntero.
1168
1169 .. flt:: fig:sol-ptrmap-example
1170
1171    Ejemplo de estructura de información de tipos generada para el tipo ``S``
1172
1173    .. aafig::
1174       :textual:
1175       :aspect: 55
1176       :scale: 110
1177
1178         /---- "bit de 'end1'"                                 -\
1179         |                                                      | "Significado"
1180         |              /---- "bit de 'middle'"                 | "de bits"
1181         |              |                                       | "en la"
1182         |    "bits de" |    "bits de"  /---- "bit de 'begin1'" | "primera"
1183         |     "'ints'" |    "'bytes'"  |                       | "palabra"
1184         |/------------\|/-------------\|                      -/
1185         V|            |V|             |V
1186       +----------------------------------+
1187       | 00000000000000000000000000100100 | "Tamaño en cantidad de palabras (36)"
1188       +==================================+ --\
1189       | 10000000000000010000000000000001 |   | "Bits que indican si hay que"
1190       +----------------------------------+   | "escanear una palabra según"
1191       | 00000000000000000000000000001101 |   | "su posición"
1192       +==================================+ --+
1193       | 10000000000000010000000000000001 |   | "Bits que indican si hay un"
1194       +----------------------------------+   | "puntero en la palabra según"
1195       | 00000000000000000000000000001001 |   | "su posición"
1196       +----------------------------------+ --/
1197         |                          |AAAA
1198         \--------------------------/||||                      -\
1199               "bits de relleno"     ||||                       |
1200                                     ||||                       | "Significado"
1201                  "bit de 's'"       ||||                       | "de bits"
1202                     |               ||||                       | "en la"
1203                     \---------------/||\---- "bit de 'begin2'" | "segunda"
1204                                      ||                        | "palabra"
1205                      /---------------/\---- "bit de 'i'"       |
1206                      |                                         |
1207                   "bit de 'u'"                                -/
1208
1209 Si una implementación quisiera mover memoria (ver :ref:`gc_moving`), debería
1210 mantener inmóvil a cualquier objeto que sea apuntado por una palabra de estas
1211 características, ya que no es seguro actualizar la palabra con la nueva
1212 posición el objeto movido. Es por esta razón que se provee desglosada la
1213 información sobre lo que hay que escanear, y lo que es realmente un puntero
1214 (que puede ser actualizado de forma segura por el recolector de ser
1215 necesario).
1216
1217 Implementación en el recolector
1218 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1219 La implementación está basada en la idea original de David Simcha, pero
1220 partiendo de la implementación de Vincent Lang (que está basada en Tango_)
1221 y consiste en almacenar el puntero a la estructura con la descripción del tipo
1222 generada por el compilador al final del bloque de datos. Este puntero solo se
1223 almacena si el bloque solicitado no tiene el atributo ``NO_SCAN``, dado que en
1224 ese caso no hace falta directamente escanear ninguna palabra del bloque.
1225
1226 En la figura :vref:`fig:sol-ptrmap-blk` se puede ver, como continuación del
1227 ejemplo anterior, como se almacenaría en memoria un objeto del tipo ``S``.
1228
1229 .. flt:: fig:sol-ptrmap-blk
1230
1231    Ejemplo de bloque que almacena un objeto de tipo ``S`` con información de
1232    tipo
1233
1234    .. aafig::
1235       :scale: 110
1236
1237       |                                                                |
1238       +------------------------ 256 bytes -----------------------------+
1239       |                                                                |
1240
1241       +----------------------------------+-----------------------+-----+
1242       |                                  |                       |     |
1243       | Objeto                           | Desperdicio           | Ptr |
1244       |                                  |                       |     |
1245       +----------------------------------+-----------------------+-----+
1246
1247       |                                  |                       |     |
1248       +------------ 144 bytes -----------+------ 108 bytes ------+- 4 -+
1249       |                                  |                       |     |
1250
1251 Un problema evidente de este esquema es que si el tamaño de un objeto se
1252 aproxima mucho al tamaño de bloque (difiere en menos de una palabra), el
1253 objeto ocupará el doble de memoria.
1254
1255 El algoritmo de marcado se cambia de la siguiente forma::
1256
1257    // Agregado
1258    global conservative_scan = [1, 1, 0]
1259
1260    // Agregado
1261    function must_scan_word(pos, bits) is
1262       return bits[pos / BITS_PER_WORD] & (1 << (pos % BITS_PER_WORD))
1263
1264    function mark_range(begin, end, ptrmap) is             // Modificado
1265       number_of_words_in_type = ptrmap[0]                 // Agregado
1266       size_t* scan_bits = ptrmap + 1                      // Agregado
1267       pointer = begin
1268       while pointer < end
1269          foreach word_pos in 0..number_of_words_in_type   //
1270             if not must_scan_word(n, scan_bits)           // Agregado
1271                continue                                   //
1272             [pool, page, block] = find_block(pointer)
1273             if block is not null and block.mark is false
1274                block.mark = true
1275                if block.noscan is false
1276                   block.scan = true
1277                   global more_to_scan = true
1278          pointer += number_of_words_in_type               // Modificado
1279
1280    function mark_heap() is
1281       while global more_to_scan
1282          global more_to_scan = false
1283          foreach pool in heap
1284             foreach page in pool
1285                if page.block_size <= PAGE // saltea FREE y CONTINUATION
1286                   foreach block in page
1287                      if block.scan is true
1288                         block.scan = false
1289                         if page.block_size is PAGE // obj grande //
1290                            begin = cast(byte*) page              //
1291                            end = find_big_object_end(pool, page) //
1292                         else // objeto pequeño                   //
1293                            begin = block.begin                   //
1294                            end = block.end                       // Modificado
1295                         ptrmap = global conservative_scan        //
1296                         if NO_SCAN not in block.attrs            //
1297                            end -= size_t.sizeof                  //
1298                            ptrmap = cast(size_t*) *end           //
1299                         mark_range(begin, end, ptrmap)           //
1300
1301    function mark_static_data() is
1302       mark_range(static_data.begin, static_data.end,
1303             global conservative_scan)                // Agregado
1304
1305    function mark_stacks() is
1306       foreach thread in threads
1307          mark_range(thread.stack.begin, thread.stack.end,
1308                global conservative_scan)                  // Agregado
1309
1310    function mark_user_roots() is
1311       foreach root_range in user_roots
1312          mark_range(root_range.begin, root_range.end,
1313                global conservative_scan)              // Agregado
1314
1315 Las funciones de asignación de memoria se modifican de forma similar, para
1316 guardar el puntero a la información de tipos. Esta implementación utiliza solo
1317 la información sobre que palabras hay que tratar como punteros (deben ser
1318 escaneadas); la información sobre qué palabras son efectivamente punteros no
1319 se utiliza ya que no se mueven celdas.
1320
1321 El algoritmo sigue siendo correcto, puesto que solamente se dejan de escanear
1322 palabras que el compilador sabe que no pueden ser punteros. Si bien el
1323 lenguaje permite almacenar punteros en una variable que no lo sea, esto es
1324 comportamiento indefinido por lo tanto un programa que lo hace no es
1325 considerado correcto, por lo cual el recolector tampoco debe ser correcto en
1326 esas circunstancias.
1327
1328 Cabe destacar que la información de tipos solo se provee para objetos
1329 almacenados en el *heap*, el área de memoria estática, registros del
1330 procesador y la pila de todos los hilos siguen siendo escaneados de forma
1331 completamente conservativa. Se puede forzar el escaneo puramente conservativo
1332 utilizando la opción ``conservative`` (ver :ref:`sol_config_spec`).
1333
1334
1335 .. _sol_fork:
1336
1337 Marcado concurrente
1338 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1339
1340 Finalmente se procede al objetivo primario de este trabajo, hacer que la fase
1341 más costosa del recolector (el marcado) pueda correr de manera concurrente con
1342 el *mutator*, con el objeto principal de disminuir el tiempo de pausa.
1343
1344 Cabe aclarar, una vez más, que si bien los recolectores concurrentes buscan
1345 disminuir solo el tiempo de *stop-the-world*, en este caso es también
1346 fundamental disminuir el tiempo máximo que está tomado el *lock* global, dado
1347 que ese tiempo puede convertirse en una pausa para todos los threads que
1348 requieran servicios del recolector.
1349
1350 Se decide basar la implementación en el *paper* "Non-intrusive Cloning Garbage
1351 Collector with Stock Operating System Support" [RODR97]_ por las siguientes
1352 razones principales:
1353
1354 * Su implementación encaja de forma bastante natural con el diseño del
1355   recolector actual, por lo que requiere pocos cambios, lo que hace más
1356   factible su aceptación.
1357 * Está basado en la llamada al sistema :manpage:`fork(2)`, que no solo está
1358   muy bien soportada (y de manera muy eficiente) en Linux_, debe estar
1359   soportada en cualquier sistema operativo :term:`POSIX`.
1360 * No necesita instrumentar el código incluyendo barreras de memoria para
1361   informar al recolector cuando cambia el grafo de conectividad. Este es un
1362   aspecto fundamental, dada la filosofía de D_ de no pagar el precio de cosas
1363   que no se usan. La penalización en la eficiencia solo se paga cuando corre
1364   el recolector. Este aspecto también es crítico a la hora de evaluar la
1365   aceptación de la solución por parte de la comunidad.
1366 * Dada su sencillez general, no es difícil ofrecer el algoritmo concurrente
1367   como una opción, de manera que el usuario pueda optar por usarlo o no.
1368
1369 Llamada al sistema *fork*
1370 ^^^^^^^^^^^^^^^^^^^^^^^^^
1371 El término *fork* proviene del inglés y significa *tenedor* de manera textual,
1372 pero se lo utiliza como analogía de una bifurcación. La operación crea una
1373 copia (llamada *hijo*) del proceso que la ejecuta (llamado *padre*).
1374
1375 El punto más importante es que se crea un espacio de direcciones de memoria
1376 separado para el proceso hijo y una copia exacta de todos los segmentos de
1377 memoria del proceso padre. Es por esto que cualquier modificación que se haga
1378 en el proceso padre, no se refleja en el proceso hijo (y viceversa), a menos
1379 que la memoria sea compartida entre los procesos de forma explícita.
1380
1381 Esto, sin embargo, no significa que la memoria física sea realmente duplicada;
1382 en general todos los sistemas operativos modernos (como Linux_) utilizan una
1383 técnica llamada *COW* (de *copy-on-write* en inglés, *copiar-al-escribir* en
1384 castellano) que retrasa la copia de memoria hasta que alguno de los dos
1385 procesos escribe en un segmento. Recién en ese momento el sistema operativo
1386 realiza la copia de **ese segmento solamente**. Es por esto que la operación
1387 puede ser muy eficiente, y la copia de memoria es proporcional a la cantidad
1388 de cambios que hayan.
1389
1390 :manpage:`fork(2)` tiene otra propiedad importante de mencionar: detiene todos
1391 los hilos de ejecución en el proceso hijo. Es decir, el proceso hijo se crear
1392 con un solo hilo (el hilo que ejecutó la operación de :manpage:`fork(2)`).
1393
1394 Algoritmo
1395 ^^^^^^^^^
1396 Lo que propone el algoritmo es muy sencillo, utilizar la llamada al sistema
1397 :manpage:`fork(2)` para crear una *fotografía* de la memoria del proceso en un
1398 nuevo proceso. En el proceso padre sigue corriendo el *mutator* y en el
1399 proceso hijo se corre la fase de marcado. El *mutator* puede modificar el
1400 grafo de conectividad pero los cambios quedan aislados el hijo (el marcado),
1401 que tiene una visión consistente e inmutable de la memoria. El sistema
1402 operativo duplica las páginas que modifica el padre bajo demanda, por lo tanto
1403 la cantidad de memoria física realmente copiada es proporcional a la cantidad
1404 y dispersión de los cambios que haga el *mutator*.
1405
1406 La corrección del algoritmo se mantiene gracias a que la siguiente invariante
1407 se preserva:
1408
1409    Cuando una celda se convierte en basura, permanece como basura hasta ser
1410    reciclada por el recolector.
1411
1412 Es decir, el *mutator* no puede *resucitar* una celda *muerta* y esta
1413 invariante se mantiene al correr la fase de marcado sobre una vista inmutable
1414 de la memoria. El único efecto introducido es que el algoritmo toma una
1415 aproximación más conservativa. Es decir, lo que sí puede pasar es que una
1416 celda que pasó a estar *muerta* una vez que la fase de marcado se inició, pero
1417 antes de que ésta termine, la celda no se reciclará hasta la próxima
1418 recolección, dado que este algoritmo no incluye una comunicación entre
1419 *mutator* y recolector para notificar cambios en el grafo de conectividad.
1420 Pero esto no afecta la corrección del algoritmo, ya que un recolector es
1421 correcto cuando nunca recicla celdas *vivas*.
1422
1423 La única comunicación necesaria entre el *mutator* y el recolector son los
1424 bits de marcado (ver :ref:`dgc_impl`), dado que la fase de barrido debe correr
1425 en el proceso padre. No es necesaria ningún tipo de sincronización entre
1426 *mutator* y recolector más allá de que uno espera a que el otro finalice.
1427
1428 Además de almacenar el conjunto de bits ``mark`` en memoria compartida entre
1429 el proceso padre e hijo (necesario para la fase de barrido), las
1430 modificaciones necesarias para hacer la fase de marcado concurrente son las
1431 siguientes [#solforkerr]_::
1432
1433    function collect() is
1434       stop_the_world()
1435       fflush(null) // evita que se duplique la salida de los FILE* abiertos
1436       child_pid = fork()
1437       if child_pid is 0 // proceso hijo
1438          mark_phase()
1439          exit(0) // termina el proceso hijo
1440       // proceso padre
1441       start_the_world()
1442       wait(child_pid)
1443       sweep()
1444
1445    function mark_phase() is
1446       global more_to_scan = false
1447       // Borrado: stop_the_world()
1448       clear_mark_scan_bits()
1449       mark_free_lists()
1450       mark_static_data()
1451       push_registers_into_stack()
1452       thread_self.stack.end = get_stack_top()
1453       mark_stacks()
1454       pop_registers_from_stack()
1455       mark_user_roots()
1456       mark_heap()
1457       // Borrado: start_the_world()
1458
1459 Como se puede observar, el cambio es extremadamente sencillo. Sigue siendo
1460 necesario un tiempo mínimo de pausa (básicamente el tiempo que tarda la
1461 llamada al sistema operativo :manpage:`fork(2)`) para guardar una vista
1462 consistente de los registros del CPU y *stacks* de los hilos. Si bien el
1463 conjunto de bits ``mark`` es compartido por el proceso padre e hijo dado que
1464 es necesario para *comunicar* las fases de marcado y barrido, cabe notar que
1465 nunca son utilizados de forma concurrente (la fase de barrido espera que la
1466 fase de marcado termine antes de usar dichos bits), por lo tanto no necesitan
1467 ningún tipo de sincronización y nunca habrá más de una recolección en proceso
1468 debido al *lock* global del recolector.
1469
1470 A pesar de que con estos cambios el recolector técnicamente corre de forma
1471 concurrente, se puede apreciar que para un programa con un solo hilo el
1472 tiempo máximo de pausa seguirá siendo muy grande, incluso más grande que antes
1473 dado el trabajo extra que impone crear un nuevo proceso y duplicar las páginas
1474 de memoria modificadas. Lo mismo le pasará a cualquier hilo que necesite hacer
1475 uso del recolector mientras hay una recolección en proceso, debido al *lock*
1476 global.
1477
1478 Para bajar este tiempo de pausa se experimenta con dos nuevas mejoras, que se
1479 describen a continuación, cuyo objetivo es correr la fase de marcado de forma
1480 concurrente a **todos** los hilos, incluyendo el hilo que la disparó.
1481
1482 .. [#solforkerr] Se omite el manejo de errores y la activación/desactivación
1483    del marcado concurrente a través de opciones del recolector para facilitar
1484    la comprensión del algoritmo y los cambios realizados. Si devuelve con
1485    error la llamada a ``fork()`` o ``waitpid()``, se vuelve al esquema
1486    *stop-the-world* como si se hubiera desactivado el marcado concurrente
1487    utilizando la opción del recolector ``fork=0``.
1488
1489
1490 .. _sol_eager_alloc:
1491
1492 Creación ansiosa de *pools* (*eager allocation*)
1493 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1494 Esta mejora, que puede ser controlada a través de la opción ``eager_alloc``
1495 (ver :ref:`sol_config_spec`), consiste en crear un nuevo *pool* cuando un
1496 pedido de memoria no puede ser satisfecho, justo después de lanzar la
1497 recolección. Esto permite al recolector satisfacer la petición de memoria
1498 inmediatamente, corriendo la fase de marcado de forma realmente concurrente,
1499 incluso para programas con un solo hilo o programas cuyos hilos usan
1500 frecuentemente servicios del recolector. El precio a pagar es un mayor uso de
1501 memoria de forma temporal (y el trabajo extra de crear y eliminar *pools* más
1502 frecuentemente), pero es esperable que el tiempo máximo de pausa **real** se
1503 vea drásticamente disminuido.
1504
1505 A simple vista las modificaciones necesarias para su implementación parecieran
1506 ser las siguientes::
1507
1508    // Agregado
1509    global mark_pid = 0
1510
1511    // Agregado
1512    function mark_is_running() is
1513       return global mark_pid != 0
1514
1515    function collect() is
1516       if mark_is_running()                      //
1517          finished = try_wait(global mark_pid)   //
1518          if finished                            // Agregado
1519             mark_pid = 0                        //
1520             sweep()                             //
1521          return                                 //
1522       stop_the_world()
1523       child_pid = fork()
1524       fflush(null)
1525       if child_pid is 0 // proceso hijo
1526          mark_phase()
1527          exit(0)
1528       // proceso padre
1529       start_the_world()
1530       // Borrado: wait(child_pid)
1531       global mark_pid = child_pid
1532
1533 Sin embargo con sólo estas modificaciones el algoritmo deja de ser correcto,
1534 ya que tres cosas problemáticas pueden suceder:
1535
1536 1. Puede llamarse a la función ``minimize()`` mientras hay una fase de marcado
1537    corriendo en paralelo. Esto puede provocar que se libere un *pool* mientras
1538    se lo está usando en la fase de marcado, lo que no sería un problema
1539    (porque el proceso de marcado tiene una copia) si no fuera porque los bits
1540    de marcado, que son compartidos por los procesos, se liberan con el *pool*.
1541 2. Si un bloque libre es asignado después de que la fase de marcado comienza,
1542    pero antes de que termine, ese bloque será barrido dado la función
1543    ``rebuild_free_lists()`` puede reciclar páginas si todos sus bloques tienen
1544    el bit ``freebits`` activo (ver :ref:`dgc_algo_sweep`).
1545 3. El *pool* creado ansiosamente, tendrá sus bits de marcado sin activar, por
1546    lo que en la fase de barrido será interpretado como memoria libre, incluso
1547    cuando puedan estar siendo utilizados por el *mutator*.
1548
1549 El punto 1 sencillamente hace que el programa finalice con una violación de
1550 segmento (en el mejor caso) y 2 y 3 pueden desembocar en la liberación de una
1551 celda alcanzable por el *mutator*.
1552
1553 El punto 1 se resuelve a través de la siguiente modificación::
1554
1555    function minimize() is
1556       if mark_is_running()                            // Agregado
1557          return                                       //
1558       for pool in heap
1559          all_free = true
1560          for page in pool
1561             if page.block_size is not FREE
1562                all_free = false
1563                break
1564          if all_free is true
1565             free(pool.pages)
1566             free(pool)
1567             heap.remove(pool)
1568
1569 La resolución del punto 2 es un poco más laboriosa, ya que hay que mantener
1570 actualizado los ``freebits``, de forma que las celdas asignadas después de
1571 empezar la fase de marcado no sean barridas por tener ese bit activo::
1572
1573    function new_big(size) is
1574       number_of_pages = ceil(size / PAGE_SIZE)
1575       pages = find_pages(number_of_pages)
1576       if pages is null
1577          collect()
1578          pages = find_pages(number_of_pages)
1579          if pages is null
1580             minimize()
1581             pool = new_pool(number_of_pages)
1582             if pool is null
1583                return null
1584             pages = assign_pages(pool, number_of_pages)
1585       pages[0].block.free = true                         // Agregado
1586       pages[0].block_size = PAGE
1587       foreach page in pages[1 .. end]
1588          page.block_size = CONTINUATION
1589       return pages[0]
1590
1591    function assign_page(block_size) is
1592       foreach pool in heap
1593          foreach page in pool
1594             if page.block_size is FREE
1595                page.block_size = block_size
1596                foreach block in page
1597                   block.free = true                         // Agregado
1598                   free_lists[page.block_size].link(block)
1599
1600    function mark_phase() is
1601       global more_to_scan = false
1602       // Borrado: clear_mark_scan_bits()
1603       // Borrado: mark_free_lists()
1604       clear_scan_bits()                         // Agregado
1605       mark_free()                               //
1606       mark_static_data()
1607       push_registers_into_stack()
1608       thread_self.stack.end = get_stack_top()
1609       mark_stacks()
1610       pop_registers_from_stack()
1611       mark_user_roots()
1612       mark_heap()
1613
1614    // Agregado
1615    function clear_scan_bits() is
1616       // La implementación real limpia los bits en bloques de forma eficiente
1617       foreach pool in heap
1618          foreach page in pool
1619             foreach block in page
1620                block.scan = false
1621
1622    // Agregado
1623    function mark_free() is
1624       // La implementación real copia los bits en bloques de forma eficiente
1625       foreach pool in heap
1626          foreach page in pool
1627             foreach block in page
1628                block.mark = block.free
1629
1630    function free_big_object(pool, page) is
1631       pool_end = cast(byte*) pool.pages + (PAGE_SIZE * pool.number_of_pages)
1632       do
1633          page.block_size = FREE
1634          page.block.free = true                 // Agregado
1635          page = cast(byte*) page + PAGE_SIZE
1636       while page < pool_end and page.block_size is CONTINUATION
1637
1638    function new(size, attrs) is
1639       block_size = find_block_size(size)
1640       if block_size < PAGE
1641          block = new_small(block_size)
1642       else
1643          block = new_big(size)
1644       if block is null
1645          throw out_of_memory
1646       if final in attrs
1647          block.final = true
1648       if noscan in attrs
1649          block.noscan = true
1650       block.free = false         // Agregado
1651       return cast(void*) block
1652
1653    funciones new_pool(number_of_pages = 1) is
1654       pool = alloc(pool.sizeof)
1655       if pool is null
1656          return null
1657       pool.number_of_pages = number_of_pages
1658       pool.pages = alloc(number_of_pages * PAGE_SIZE)
1659       if pool.pages is null
1660          free(pool)
1661          return null
1662       heap.add(pool)
1663       foreach page in pool
1664          page.block_size = FREE
1665          foreach block in page      //
1666             block.free = true       // Agregado
1667             block.mark = true       //
1668       return pool
1669
1670 Finalmente, el punto número tres puede ser solucionado con el siguiente
1671 pequeño cambio::
1672
1673    funciones new_pool(number_of_pages = 1) is
1674       pool = alloc(pool.sizeof)
1675       if pool is null
1676          return null
1677       pool.number_of_pages = number_of_pages
1678       pool.pages = alloc(number_of_pages * PAGE_SIZE)
1679       if pool.pages is null
1680          free(pool)
1681          return null
1682       heap.add(pool)
1683       foreach page in pool
1684          page.block_size = FREE
1685          foreach block in page      // Agregado
1686             block.mark = true       //
1687       return pool
1688
1689 La solución es conservativa porque, por un lado evita la liberación de *pools*
1690 mientras haya una recolección en curso (lo que puede hacer que el consumo de
1691 memoria sea un poco mayor al requerido) y por otro asegura que, como se
1692 mencionó anteriormente, los cambios hechos al grafo de conectividad luego de
1693 iniciar la fase de marcado y antes de que ésta termine, no serán detectados
1694 por el recolector hasta la próxima recolección (marcar todos los bloques de
1695 un nuevo *pool* como el bit ``mark`` asegura que que la memoria no sea
1696 recolectada por la fase de barrido cuando termine el marcado).
1697
1698 Estas modificaciones son las que hacen que el algoritmo siga siendo correcto,
1699 asegurando que no se van a liberar celdas *vivas* (a expensas de diferir la
1700 liberación de algunas celdas *muertas* por algún tiempo).
1701
1702
1703 .. _sol_early_collect:
1704
1705 Recolección temprana (*early collection*)
1706 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1707 Esta mejora, que puede ser controlada a través de la opción ``early_collect``
1708 (ver :ref:`sol_config_spec`), consiste en lanzar una recolección preventiva,
1709 antes de que una petición de memoria falle. El momento en que se lanza la
1710 recolección es controlado por la opción ``min_free`` (ver :ref:`sol_ocup`).
1711
1712 De esta forma también puede correr de forma realmente concurrente el *mutator*
1713 y el recolector, al menos hasta que se acabe la memoria, en cuyo caso, a menos
1714 que la opción ``eager_alloc`` (ver :ref:`sol_eager_alloc`) también esté
1715 activada, se deberá esperar a que la fase de marcado termine para recuperar
1716 memoria en la fase de barrido.
1717
1718 Para facilitar la comprensión de esta mejora se muestran sólo los cambios
1719 necesarios si no se utiliza la opción ``eager_alloc``::
1720
1721    function collect(early = false) is  // Modificado
1722       if mark_is_running()
1723          finished = try_wait(global mark_pid)
1724          if finished
1725             mark_pid = 0
1726             sweep()
1727             return                     //
1728          else if early                 // Agregado
1729             return                     //
1730       stop_the_world()
1731       fflush(null)
1732       child_pid = fork()
1733       if child_pid is 0 // proceso hijo
1734          mark_phase()
1735          exit(0)
1736       // proceso padre
1737       start_the_world()
1738       if early                         //
1739          global mark_pid = child_pid   //
1740       else                             // Agregado
1741          wait(child_pid)               //
1742          sweep()                       //
1743
1744    // Agregado
1745    function early_collect() is
1746       if not collect_in_progress() and (percent_free < min_free)
1747          collect(true)
1748
1749    function new(size, attrs) is
1750       block_size = find_block_size(size)
1751       if block_size < PAGE
1752          block = new_small(block_size)
1753       else
1754          block = new_big(size)
1755       if block is null
1756          throw out_of_memory
1757       if final in attrs
1758          block.final = true
1759       if noscan in attrs
1760          block.noscan = true
1761       early_collect()               // Agregado
1762       return cast(void*) block
1763
1764 Es de esperarse que cuando no está activa la opción ``eager_alloc`` por un
1765 lado el tiempo de pausa máximo no sea tan chico como cuando sí lo está (dado
1766 que si la recolección no se lanza de forma suficientemente temprana se va
1767 a tener que esperar que la fase de marcado termine), y por otro que se hagan
1768 más recolecciones de lo necesario (cuando pasa lo contrario, se recolecta más
1769 temprano de lo que se debería). Sin embargo, también es de esperarse que el
1770 consumo de memoria sea un poco menor que al usar la opción ``eager_alloc``.
1771
1772 En cuanto a la corrección del algoritmo, éste solamente presenta los problemas
1773 número 1 y 2 mencionados en :ref:`sol_eager_alloc`, dado que jamás se crean
1774 nuevos *pools* y la solución es la ya presentada, por lo tanto el algoritmo
1775 sigue siendo correcto con los cuidados pertinentes.
1776
1777
1778
1779 Resultados
1780 ----------------------------------------------------------------------------
1781
1782 Los resultados de las modificación propuestas en la sección anterior (ver
1783 :ref:`sol_mod`) se evalúan utilizando el conjunto de pruebas mencionado en la
1784 sección :ref:`sol_bench`).
1785
1786 En esta sección se describe la forma en la que el conjunto de pruebas es
1787 utilizado, la forma en la que se ejecutan los programas para recolectar dichos
1788 resultados y las métricas principales utilizadas para analizarlos.
1789
1790 A fines prácticos, y haciendo alusión al nombre utilizado por Tango_, en esta
1791 sección se utiliza el nombre **TBGC** (acrónimo para el nombre en inglés
1792 *Tango Basic Garbage Collector*) para hacer referencia al recolector original
1793 provisto por Tango_ 0.99.9 (que, recordamos, es el punto de partida de este
1794 trabajo). Por otro lado, y destacando la principal modificación propuesta por
1795 este trabajo, haremos referencia al recolector resultante de éste utilizando
1796 el nombre **CDGC** (acrónimo para el nombre en inglés *Concurrent D Garbage
1797 Collector*).
1798
1799
1800 Ejecución del conjunto de pruebas
1801 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1802
1803 Dado el indeterminismo inherente a los sistemas operativos de tiempo
1804 compartido modernos, se hace un particular esfuerzo por obtener resultados lo
1805 más estable posible.
1806
1807 Hardware y software utilizado
1808 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1809 Para realizar las pruebas se utiliza el siguiente hardware:
1810
1811 * Procesador Intel(R) Core(TM)2 Quad CPU Q8400 @ 2.66GHz.
1812 * 2GiB de memoria RAM.
1813
1814 El entorno de software es el siguiente:
1815
1816 * Sistema operativo Debian_ Sid (para arquitectura *amd64*).
1817 * Linux_ 2.6.35.7.
1818 * DMD_ 1.063 modificado para proveer información de tipos al recolector (ver
1819   :ref:`sol_precise`).
1820 * *Runtime* Tango_ 0.99.9 modificado para utilizar la información de tipos
1821   provista por el compilador modificado.
1822 * GCC_ 4.4.5.
1823 * Embedded GNU_ C Library 2.11.2.
1824
1825 Si bien el sistema operativo utiliza arquitectura *amd64*, dado que DMD_
1826 todavía no soporta 64 bits, se compila y corren los programas de D_ en 32
1827 bits.
1828
1829 Opciones del compilador
1830 ^^^^^^^^^^^^^^^^^^^^^^^
1831 Los programas del conjunto de pruebas se compilan utilizando las siguientes
1832 opciones del compilador DMD_:
1833
1834 ``-O``
1835    Aplica optimizaciones generales.
1836
1837 ``-inline``
1838    Aplica la optimización de expansión de funciones. Consiste en sustituir la
1839    llamada a función por el cuerpo de la función (en general solo para
1840    funciones pequeñas).
1841
1842 ``-release``
1843    No genera el código para verificar pre y post-condiciones, invariantes de
1844    representación, operaciones fuera de los límites de un arreglo y
1845    *assert*\ 's en general (ver :ref:`d_dbc`).
1846
1847 Parámetros de los programas
1848 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
1849 Los programas de prueba se ejecutan siempre con los mismos parámetros (a menos
1850 que se especifique lo contrario), que se detallan a continuación.
1851
1852 .. highlight:: none
1853
1854 ``conalloc``
1855    ``40 4 bible.txt``
1856
1857    Procesa 40 veces un archivo de texto plano (de 4MiB de tamaño) [#solbible]_
1858    utilizando 4 hilos (más el principal).
1859
1860 ``concpu``
1861    ``40 4 bible.txt``
1862
1863    Procesa 40 veces un archivo de texto plano (de 4MiB de tamaño) [#solbible]_
1864    utilizando 4 hilos (más el principal).
1865
1866 ``split``
1867    ``bible.txt 2``
1868
1869    Procesa dos veces un archivo de texto plano (de 4MiB de tamaño)
1870    [#solbible]_.
1871
1872 ``sbtree``
1873    ``16``
1874
1875    Construyen árboles con profundidad máxima 16.
1876
1877 ``bh``
1878    ``-b 4000``
1879
1880    Computa las interacciones gravitatorias entre 4.000 cuerpos.
1881
1882 ``bisort``
1883    ``-s 2097151``
1884
1885    Ordena alrededor de 2 millones de números (exactamente :math:`2^21
1886    = 2097151`).
1887
1888 ``em3d``
1889    ``-n 4000 -d 300 -i 74``
1890
1891    Realiza 74 iteraciones para modelar 4.000 nodos con grado 300.
1892
1893 ``tsp``
1894    ``-c 1000000``
1895
1896    Resuelve el problema del viajante a través de una heurística para un
1897    millón de ciudades.
1898
1899 ``voronoi``
1900    ``-n 30000``
1901
1902    Se construye un diagrama con 30.000 nodos.
1903
1904 ``dil``
1905    ``ddoc $dst_dir -hl --kandil -version=Tango -version=TangoDoc
1906    -version=Posix -version=linux $tango_files``
1907
1908    Genera la documentación de todo el código fuente de Tango_ 0.99.9, donde
1909    ``$dst_dir`` es el directorio donde almacenar los archivos generados
1910    y ``$tango_files`` es la lista de archivos fuente de Tango_.
1911
1912 El resto de los programas se ejecutan sin parámetros (ver :ref:`sol_bench`
1913 para una descripción detallada sobre cada uno).
1914
1915 .. [#solbible] El archivo contiene la Biblia completa, la versión traducida al
1916    inglés autorizada por el Rey Jaime o Jacobo (*Authorized King James
1917    Version* en inglés). Obtenida de: http://download.o-bible.com:8080/kjv.gz
1918
1919 Recolectores y configuraciones utilizadas
1920 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1921 En general se presentan resultados para TBGC y varias configuraciones de CDGC,
1922 de manera de poder tener una mejor noción de que mejoras y problemas puede
1923 introducir cada una de las modificaciones más importantes.
1924
1925 CDGC se utiliza con siguientes configuraciones:
1926
1927 .. highlight:: none
1928
1929 cons
1930    En modo conservativo. Específicamente, utilizando el juego de opciones::
1931
1932       conservative=1:fork=0:early_collect=0:eager_alloc=0
1933
1934 prec
1935    En modo preciso (ver :ref:`sol_precise`). Específicamente, utilizando el
1936    juego de opciones::
1937
1938       conservative=0:fork=0:early_collect=0:eager_alloc=0
1939
1940 fork
1941    En modo preciso activando el marcado concurrente (ver :ref:`sol_fork`).
1942    Específicamente, utilizando el juego de opciones::
1943
1944       conservative=0:fork=1:early_collect=0:eager_alloc=0
1945
1946 ecol
1947    En modo preciso activando el marcado concurrente con recolección temprana
1948    (ver :ref:`sol_early_collect`).  Específicamente, utilizando el juego de
1949    opciones::
1950
1951       conservative=0:fork=1:early_collect=1:eager_alloc=0
1952
1953 eall
1954    En modo preciso activando el marcado concurrente con creación ansiosa de
1955    *pools* (ver :ref:`sol_eager_alloc`).  Específicamente, utilizando el juego
1956    de opciones::
1957
1958       conservative=0:fork=1:early_collect=0:eager_alloc=1
1959
1960 todo
1961    En modo preciso activando el marcado concurrente con recolección temprana
1962    y creación ansiosa de *pools*.  Específicamente, utilizando el juego de
1963    opciones::
1964
1965       conservative=0:fork=1:early_collect=1:eager_alloc=1
1966
1967 Métricas utilizadas
1968 ^^^^^^^^^^^^^^^^^^^
1969 Para analizar los resultados se utilizan varias métricas. Las más importantes
1970 son:
1971
1972 * Tiempo total de ejecución.
1973 * Tiempo máximo de *stop-the-world*.
1974 * Tiempo máximo de pausa real.
1975 * Cantidad máxima de memoria utilizada.
1976 * Cantidad total de recolecciones realizadas.
1977
1978 El tiempo total de ejecución es una buena medida del **rendimiento** general
1979 del recolector, mientras que la cantidad total de recolecciones realizadas
1980 suele ser una buena medida de su **eficacia** [#soleficacia]_.
1981
1982 Los tiempos máximos de pausa, *stop-the-world* y real, son una buena medida de
1983 la **latencia** del recolector; el segundo siendo una medida más realista dado
1984 que es raro que los demás hilos no utilicen servicios del recolector mientras
1985 hay una recolección en curso. Esta medida es particularmente importante para
1986 programas que necesiten algún nivel de ejecución en *tiempo-real*.
1987
1988 En general el consumo de tiempo y espacio es un compromiso, cuando se consume
1989 menos tiempo se necesita más espacio y viceversa. La cantidad máxima de
1990 memoria utilizada nos da un parámetro de esta relación.
1991
1992 .. [#soleficacia] Esto no es necesariamente cierto para recolectores con
1993    particiones (ver :ref:`gc_part`) o incrementales (ver :ref:`gc_inc`), dado
1994    que en ese caso podría realizar muchas recolecciones pero cada una muy
1995    velozmente.
1996
1997 Métodología de medición
1998 ^^^^^^^^^^^^^^^^^^^^^^^
1999 Para medir el tiempo total de ejecución se utiliza el comando
2000 :manpage:`time(1)` con la especificación de formato ``%e``, siendo la medición
2001 más realista porque incluye el tiempo de carga del ejecutable, inicialización
2002 del *runtime* de D_ y del recolector.
2003
2004 Todas las demás métricas se obtienen utilizando la salida generada por la
2005 opción ``collect_stats_file`` (ver :ref:`sol_stats`), por lo que no pueden ser
2006 medidos para TBGC. Sin embargo se espera que para esos casos los resultados no
2007 sean muy distintos a CDGC utilizando la configuración **cons** (ver sección
2008 anterior).
2009
2010 Cabe destacar que las corridas para medir el tiempo total de ejecución no son
2011 las mismas que al utilizar la opción ``collect_stats_file``; cuando se mide el
2012 tiempo de ejecución no se utiliza esa opción porque impone un trabajo extra
2013 importante y perturbaría demasiado la medición del tiempo. Sin embargo, los
2014 tiempos medidos internamente al utilizar la opción ``collect_stats_file`` son
2015 muy precisos, dado que se hace un particular esfuerzo para que no se haga un
2016 trabajo extra mientras se está midiendo el tiempo.
2017
2018 Al obtener el tiempo de *stop-the-world* se ignoran los apariciones del valor
2019 ``-1``, que indica que se solicitó una recolección pero que ya había otra en
2020 curso, por lo que no se pausan los hilos realmente. Como tiempo de pausa real
2021 (ver :ref:`sol_fork` para más detalles sobre la diferencia con el tiempo de
2022 *stop-the-world*) se toma el valor del tiempo que llevó la asignación de
2023 memoria que disparó la recolección.
2024
2025 Para medir la cantidad de memoria máxima se calcula el valor máximo de la
2026 sumatoria de: memoria usada, memoria libre, memoria desperdiciada y memoria
2027 usada por el mismo recolector (es decir, el total de memoria pedida por el
2028 programa al sistema operativo, aunque no toda este siendo utilizada por el
2029 *mutator* realmente).
2030
2031 Por último, la cantidad total de recolecciones realizadas se calcula contando
2032 la cantidad de entradas del archivo generado por ``collect_stats_file``,
2033 ignorando la cabecera y las filas cuyo valor de tiempo de *stop-the-world* es
2034 ``-1``, debido a que en ese caso no se disparó realmente una recolección dado
2035 que ya había una en curso.
2036
2037 Además, ciertas pruebas se corren variando la cantidad de procesadores
2038 utilizados, para medir el impacto de la concurrencia en ambientes con un
2039 procesador solo y con múltiples procesadores. Para esto se utiliza el comando
2040 :manpage:`taskset`, que establece la *afinidad* de un proceso, *atándolo*
2041 a correr en un cierto conjunto de procesadores. Si bien las pruebas se
2042 realizan utilizando 1, 2, 3 y 4 procesadores, los resultados presentados en
2043 general se limitan a 1 y 4 procesadores, ya que no se observan diferencias
2044 sustanciales al utilizar 2 o 3 procesadores con respecto a usar 4 (solamente
2045 se ven de forma más atenuadas las diferencias entre la utilización de
2046 1 o 4 procesadores). Dado que de por sí ya son muchos los datos a procesar
2047 y analizar, agregar más resultados que no aportan información valiosa termina
2048 resultando contraproducente.
2049
2050 En los casos donde se utilizan otro tipo de métricas para evaluar aspectos
2051 particulares sobre alguna modificación se describe como se realiza la medición
2052 donde se utiliza la métrica especial.
2053
2054 .. flt:: t:sol-setarch
2055    :type: table
2056
2057    Variación entre corridas para TBGC
2058
2059    Variación entre corridas para TBGC. La medición está efectuada utilizando
2060    los valores máximo, mínimo y media estadística de 20 corridas, utilizando
2061    la siguiente métrica: :math:`\frac{max - min}{\mu}`. La medida podría
2062    realizarse utilizando el desvío estándar en vez de la amplitud máxima, pero
2063    en este cuadro se quiere ilustrar la variación máxima, no la típica.
2064
2065    .. subflt::
2066
2067       Del tiempo total de ejecución.
2068
2069       ======== ======== ======== ========
2070       Programa Normal   ``-R``   ``-L``
2071       ======== ======== ======== ========
2072       bh       0.185    0.004    0.020
2073       bigarr   0.012    0.002    0.016
2074       bisort   0.006    0.003    0.006
2075       conalloc 0.004    0.004    0.004
2076       concpu   0.272    0.291    0.256
2077       dil      0.198    0.128    0.199
2078       em3d     0.006    0.033    0.029
2079       mcore    0.009    0.009    0.014
2080       rnddata  0.015    0.002    0.011
2081       sbtree   0.012    0.002    0.012
2082       split    0.025    0.000    0.004
2083       tsp      0.071    0.068    0.703
2084       voronoi  0.886    0.003    0.006
2085       ======== ======== ======== ========
2086
2087    .. subflt::
2088
2089       Del consumo máximo de memoria.
2090
2091       ======== ======== ======== ========
2092       Programa Normal   ``-R``   ``-L``
2093       ======== ======== ======== ========
2094       bh       0.001    0.000    0.001
2095       bigarr   0.001    0.000    0.001
2096       bisort   0.000    0.000    0.000
2097       conalloc 0.753    0.000    0.001
2098       concpu   0.002    0.000    0.001
2099       dil      0.055    0.028    0.013
2100       em3d     0.000    0.001    0.001
2101       mcore    0.447    0.482    0.460
2102       rnddata  0.000    0.000    0.000
2103       sbtree   0.000    0.000    0.000
2104       split    0.000    0.000    0.000
2105       tsp      0.000    0.001    0.000
2106       voronoi  0.001    0.000    0.000
2107       ======== ======== ======== ========
2108
2109 .. flt:: fig:sol-bigarr-1cpu
2110
2111    Resultados para ``bigarr`` (utilizando 1 procesador)
2112
2113    Resultados para ``bigarr`` (utilizando 1 procesador). Se presenta el
2114    mínimos (en negro), la media centrada entre dos desvíos estándar (en gris),
2115    y el máximo (en blanco) calculados sobre 50 corridas (para tiempo de
2116    ejecución) o 20 corridas (para el resto).
2117
2118    .. subflt::
2119
2120       Tiempo de ejecución (seg)
2121
2122       .. image:: plots/time-bigarr-1cpu.pdf
2123
2124    .. subflt::
2125
2126       Cantidad de recolecciones
2127
2128       .. image:: plots/ncol-bigarr-1cpu.pdf
2129
2130    .. subflt::
2131
2132       Uso máximo de memoria (MiB)
2133
2134       .. image:: plots/mem-bigarr-1cpu.pdf
2135
2136    .. subflt::
2137
2138       *Stop-the-world* máximo (seg)
2139
2140       .. image:: plots/stw-bigarr-1cpu.pdf
2141
2142    .. subflt::
2143
2144       Pausa real máxima (seg)
2145
2146       .. image:: plots/pause-bigarr-1cpu.pdf
2147
2148 Variabilidad de los resultados entre ejecuciones
2149 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2150 Es de esperarse que haya una cierta variación en los resultados entre
2151 corridas, dada la indeterminación inherente a los sistemas operativos de
2152 tiempo compartido, que compiten por los recursos de la computadora.
2153
2154 Para minimizar esta variación se utilizan varias herramientas. En primer
2155 lugar, se corren las pruebas estableciendo máxima prioridad (-19 en Linux_) al
2156 proceso utilizando el comando :manpage:`nice(1)`. La variación en la
2157 frecuencia del reloj los procesadores (para ahorrar energía) puede ser otra
2158 fuente de variación, por lo que se usa el comando :manpage:`cpufreq-set(1)`
2159 para establecer la máxima frecuencia disponible de manera fija.
2160
2161 Sin embargo, a pesar de tomar estas precauciones, se sigue observando una
2162 amplia variabilidad entre corridas. Además se observa una variación más
2163 importante de la esperada no solo en el tiempo, también en el consumo de
2164 memoria, lo que es más extraño. Esta variación se debe principalmente a que
2165 Linux_ asigna el espacio de direcciones a los procesos con una componente
2166 azarosa (por razones de seguridad). Además, por omisión, la llamada al sistema
2167 :manpage:`mmap(2)` asigna direcciones de memoria altas primero, entregando
2168 direcciones más bajas en llamadas subsiguientes [LWN90311]_.
2169
2170 El comando :manpage:`setarch(8)` sirve para controlar éste y otros aspectos de
2171 Linux_. La opción ``-L`` hace que se utilice un esquema de asignación de
2172 direcciones antiguo, que no tiene una componente aleatoria y asigna primero
2173 direcciones bajas. La opción ``-R`` solamente desactiva la componente azarosa
2174 al momento de asignar direcciones.
2175
2176 Ambas opciones, reducen notablemente la variación en los resultados (ver
2177 cuadro :vref:`t:sol-setarch`). Esto probablemente se debe a la naturaleza
2178 conservativa del recolector, dado que la probabilidad de tener *falsos
2179 positivos* depende directamente de los valores de las direcciones de memoria,
2180 aunque las pruebas en la que hay concurrencia involucrada, se siguen viendo
2181 grandes variaciones, que probablemente estén vinculadas a problemas de
2182 sincronización que se ven expuestos gracias al indeterminismo inherente a los
2183 programas multi-hilo.
2184
2185 Si bien se obtienen resultados más estables utilizando un esquema diferente al
2186 utilizado por omisión, se decide no hacerlo dado que las mediciones serían
2187 menos realistas. Los usuarios en general no usan esta opción y se presentaría
2188 una visión más acotada sobre el comportamiento de los programas. Sin embargo,
2189 para evaluar el este efecto en los resultados, siempre que sea posible se
2190 analizan los resultados de un gran número de corridas observando
2191 principalmente su mínima, media, máxima y desvío estándar.
2192
2193
2194
2195 Resultados para pruebas sintizadas
2196 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2197
2198 A continuación se presentan los resultados obtenidos para las pruebas
2199 sintetizadas (ver :ref:`sol_bench_synth`). Se recuerda que este conjunto de
2200 resultados es útil para analizar ciertos aspectos puntuales de las
2201 modificaciones propuestas, pero en general distan mucho de como se comporta un
2202 programa real, por lo que los resultados deben ser analizados teniendo esto
2203 presente.
2204
2205 ``bigarr``
2206 ^^^^^^^^^^
2207 En la figura :vref:`fig:sol-bigarr-1cpu` se pueden observar los resultados
2208 para ``bigarr`` al utilizar un solo procesador. En ella se puede notar que el
2209 tiempo total de ejecución en general aumenta al utilizar CDGC, esto es
2210 esperable, dado esta prueba se limitan a usar servicios del recolector. Dado
2211 que esta ejecución utiliza solo un procesador y por lo tanto no se puede sacar
2212 provecho a la concurrencia, es de esperarse que el trabajo extra realizado por
2213 las modificaciones se vea reflejado en los resultados. En la
2214 :vref:`fig:sol-bigarr-4cpu` (resultados al utilizar 4 procesadores) se puede
2215 observar como al usar solamente *eager allocation* se recupera un poco el
2216 tiempo de ejecución, probablemente debido al incremento en la concurrencia
2217 (aunque no se observa el mismo efecto al usar *early collection*).
2218
2219 Observando el tiempo total de ejecución, no se esperaba un incremento tan
2220 notorio al pasar de TBGC a una configuración equivalente de CDGC **cons**,
2221 haciendo un breve análisis de las posibles causas, lo más probable parece ser
2222 el incremento en la complejidad de la fase de marcado dada capacidad para
2223 marcar de forma precisa (aunque no se use la opción, se paga el precio de la
2224 complejidad extra y sin obtener los beneficios).  Además se puede observar
2225 como el agregado de precisión al marcado mejora un poco las cosas (donde sí se
2226 obtiene rédito de la complejidad extra en el marcado).
2227
2228 .. flt:: fig:sol-bigarr-4cpu
2229
2230    Resultados para ``bigarr`` (utilizando 4 procesadores)
2231
2232    Resultados para ``bigarr`` (utilizando 4 procesadores). Se presenta el
2233    mínimos (en negro), la media centrada entre dos desvíos estándar (en gris),
2234    y el máximo (en blanco) calculados sobre 50 corridas (para tiempo de
2235    ejecución) o 20 corridas (para el resto).
2236
2237    .. subflt::
2238
2239       Tiempo de ejecución (seg)
2240
2241       .. image:: plots/time-bigarr-4cpu.pdf
2242
2243    .. subflt::
2244
2245       Cantidad de recolecciones
2246
2247       .. image:: plots/ncol-bigarr-4cpu.pdf
2248
2249    .. subflt::
2250
2251       Uso máximo de memoria (MiB)
2252
2253       .. image:: plots/mem-bigarr-4cpu.pdf
2254
2255    .. subflt::
2256
2257       *Stop-the-world* máximo (seg)
2258
2259       .. image:: plots/stw-bigarr-4cpu.pdf
2260
2261    .. subflt::
2262
2263       Pausa real máxima (seg)
2264
2265       .. image:: plots/pause-bigarr-4cpu.pdf
2266
2267 En general se observa que al usar *eager allocation* el consumo de memoria
2268 y los tiempos de pausa se disparan mientras que la cantidad de recolecciones
2269 disminuye drásticamente. Lo que se observa es que el programa es
2270 más veloz pidiendo memoria que recolectándola, por lo que crece mucho el
2271 consumo de memoria. Como consecuencia la fase de barrido (que no corre en
2272 paralelo al *mutator* como la fase de marcado) empieza a ser predominante en
2273 el tiempo de pausa por ser tan grande la cantidad de memoria a barrer. Este
2274 efecto se ve tanto al usar 1 como 4 procesadores, aunque el efecto es mucho
2275 más nocivo al usar 1 debido a la alta variabilidad que impone la competencia
2276 entre el *mutator* y recolector al correr de forma concurrente.
2277
2278 Sin embargo, el tiempo de *stop-the-world* es siempre considerablemente más
2279 pequeño al utilizar marcado concurrente en CDGC, incluso cuando se utiliza
2280 *eager allocation*, aunque en este caso aumenta un poco, también debido al
2281 incremento en el consumo de memoria, ya que el sistema operativo tiene que
2282 copiar tablas de memoria más grandes al efectuar el *fork* (ver
2283 :ref:`sol_fork`).
2284
2285 .. raw:: latex
2286
2287    \clearpage
2288
2289 .. flt:: fig:sol-concpu-1cpu
2290
2291    Resultados para ``concpu`` (utilizando 1 procesador)
2292
2293    Resultados para ``concpu`` (utilizando 1 procesador). Se presenta el
2294    mínimos (en negro), la media centrada entre dos desvíos estándar (en gris),
2295    y el máximo (en blanco) calculados sobre 50 corridas (para tiempo de
2296    ejecución) o 20 corridas (para el resto).
2297
2298    .. subflt::
2299
2300       Tiempo de ejecución (seg)
2301
2302       .. image:: plots/time-concpu-1cpu.pdf
2303
2304    .. subflt::
2305
2306       Cantidad de recolecciones
2307
2308       .. image:: plots/ncol-concpu-1cpu.pdf
2309
2310    .. subflt::
2311
2312       Uso máximo de memoria (MiB)
2313
2314       .. image:: plots/mem-concpu-1cpu.pdf
2315
2316    .. subflt::
2317
2318       *Stop-the-world* máximo (seg)
2319
2320       .. image:: plots/stw-concpu-1cpu.pdf
2321
2322    .. subflt::
2323
2324       Pausa real máxima (seg)
2325
2326       .. image:: plots/pause-concpu-1cpu.pdf
2327
2328 .. flt:: fig:sol-concpu-4cpu
2329
2330    Resultados para ``concpu`` (utilizando 4 procesadores)
2331
2332    Resultados para ``concpu`` (utilizando 4 procesadores). Se presenta el
2333    mínimos (en negro), la media centrada entre dos desvíos estándar (en gris),
2334    y el máximo (en blanco) calculados sobre 50 corridas (para tiempo de
2335    ejecución) o 20 corridas (para el resto).
2336
2337    .. subflt::
2338
2339       Tiempo de ejecución (seg)
2340
2341       .. image:: plots/time-concpu-4cpu.pdf
2342
2343    .. subflt::
2344
2345       Cantidad de recolecciones
2346
2347       .. image:: plots/ncol-concpu-4cpu.pdf
2348
2349    .. subflt::
2350
2351       Uso máximo de memoria (MiB)
2352
2353       .. image:: plots/mem-concpu-4cpu.pdf
2354
2355    .. subflt::
2356
2357       *Stop-the-world* máximo (seg)
2358
2359       .. image:: plots/stw-concpu-4cpu.pdf
2360
2361    .. subflt::
2362
2363       Pausa real máxima (seg)
2364
2365       .. image:: plots/pause-concpu-4cpu.pdf
2366
2367 ``concpu``
2368 ^^^^^^^^^^
2369 En la figura :vref:`fig:sol-concpu-1cpu` se pueden observar los resultados
2370 para ``concpu`` al utilizar un solo procesador. En ella se aprecia que el
2371 tiempo total de ejecución disminuye levemente al usar marcado concurrente
2372 mientras no se utilice *eager allocation* pero aumenta al utilizarlo.
2373
2374 Con respecto a la cantidad de recolecciones, uso máximo de memoria y tiempo de
2375 *stop-the-world* se ve un efecto similar al descripto para ``bigarr`` (aunque
2376 magnificado), pero sorprendentemente el tiempo total de pausa se dispara,
2377 además con una variabilidad sorprendente, cuando se usa marcado concurrente
2378 (pero no *eager allocation*). Una posible explicación podría ser que al
2379 realizarse el *fork*, el sistema operativo muy probablemente entregue el
2380 control del único procesador disponible al resto de los hilos que compiten por
2381 él, por lo que queda mucho tiempo pausado en esa operación aunque realmente no
2382 esté haciendo trabajo alguno (simplemente no tiene tiempo de procesador para
2383 correr). Este efecto se cancela al usar *eager allocation* dado que el
2384 *mutator* nunca se bloquea esperando que el proceso de marcado finalice.
2385
2386 Además se observa una caída importante en la cantidad de recolecciones al
2387 utilizar marcado concurrente. Esto probablemente se deba a que solo un hilo
2388 pide memoria (y por lo tanto dispara recolecciones), mientras los demás hilos
2389 también estén corriendo. Al pausarse todos los hilos por menos tiempo, el
2390 trabajo se hace más rápido (lo que explica la disminución del tiempo total de
2391 ejecución) y son necesarias menos recolecciones, por terminar más rápido
2392 también el hilo que las dispara.
2393
2394 En la :vref:`fig:sol-concpu-4cpu` se pueden ver los resultados al utilizar
2395 4 procesadores, donde el panorama cambia sustancialmente. El efecto mencionado
2396 en el párrafo anterior no se observa más (pues el sistema operativo tiene más
2397 procesadores para asignar a los hilos) pero todos los resultados se vuelven
2398 más variables. Los tiempos de *stop-the-world* y pausa real (salvo por lo
2399 recién mencionado) crecen notablemente, al igual que su variación. No se
2400 encuentra una razón evidente para esto; podría ser un error en la medición
2401 dado que al utilizar todos los procesadores disponibles del *hardware*,
2402 cualquier otro proceso que compita por tiempo de procesador puede afectarla
2403 más fácilmente.
2404
2405 El tiempo total de ejecución crece considerablemente, como se espera, dado que
2406 el programa aprovecha los múltiples hilos que pueden correr en paralelo en
2407 procesadores diferentes.
2408
2409 Sin embargo, no se encuentra una razón clara para explicar el crecimiento
2410 dramático en la cantidad de recolecciones solo al no usar marcado concurrente
2411 para 4 procesadores.
2412
2413 .. flt:: fig:sol-conalloc-1cpu
2414
2415    Resultados para ``conalloc`` (utilizando 1 procesador)
2416
2417    Resultados para ``conalloc`` (utilizando 1 procesador). Se presenta el
2418    mínimos (en negro), la media centrada entre dos desvíos estándar (en gris),
2419    y el máximo (en blanco) calculados sobre 50 corridas (para tiempo de
2420    ejecución) o 20 corridas (para el resto).
2421
2422    .. subflt::
2423
2424       Tiempo de ejecución (seg)
2425
2426       .. image:: plots/time-conalloc-1cpu.pdf
2427
2428    .. subflt::
2429
2430       Cantidad de recolecciones
2431
2432       .. image:: plots/ncol-conalloc-1cpu.pdf
2433
2434    .. subflt::
2435
2436       Uso máximo de memoria (MiB)
2437
2438       .. image:: plots/mem-conalloc-1cpu.pdf
2439
2440    .. subflt::
2441
2442       *Stop-the-world* máximo (seg)
2443
2444       .. image:: plots/stw-conalloc-1cpu.pdf
2445
2446    .. subflt::
2447
2448       Pausa real máxima (seg)
2449
2450       .. image:: plots/pause-conalloc-1cpu.pdf
2451
2452 .. flt:: fig:sol-conalloc-4cpu
2453
2454    Resultados para ``conalloc`` (utilizando 4 procesadores)
2455
2456    Resultados para ``conalloc`` (utilizando 4 procesadores). Se presenta el
2457    mínimos (en negro), la media centrada entre dos desvíos estándar (en gris),
2458    y el máximo (en blanco) calculados sobre 50 corridas (para tiempo de
2459    ejecución) o 20 corridas (para el resto).
2460
2461    .. subflt::
2462
2463       Tiempo de ejecución (seg)
2464
2465       .. image:: plots/time-conalloc-4cpu.pdf
2466
2467    .. subflt::
2468
2469       Cantidad de recolecciones
2470
2471       .. image:: plots/ncol-conalloc-4cpu.pdf
2472
2473    .. subflt::
2474
2475       Uso máximo de memoria (MiB)
2476
2477       .. image:: plots/mem-conalloc-4cpu.pdf
2478
2479    .. subflt::
2480
2481       *Stop-the-world* máximo (seg)
2482
2483       .. image:: plots/stw-conalloc-4cpu.pdf
2484
2485    .. subflt::
2486
2487       Pausa real máxima (seg)
2488
2489       .. image:: plots/pause-conalloc-4cpu.pdf
2490
2491 .. flt:: fig:sol-split-1cpu
2492
2493    Resultados para ``split`` (utilizando 1 procesador)
2494
2495    Resultados para ``split`` (utilizando 1 procesador). Se presenta el mínimos
2496    (en negro), la media centrada entre dos desvíos estándar (en gris), y el
2497    máximo (en blanco) calculados sobre 50 corridas (para tiempo de ejecución)
2498    o 20 corridas (para el resto).
2499
2500    .. subflt::
2501
2502       Tiempo de ejecución (seg)
2503
2504       .. image:: plots/time-split-1cpu.pdf
2505
2506    .. subflt::
2507
2508       Cantidad de recolecciones
2509
2510       .. image:: plots/ncol-split-1cpu.pdf
2511
2512    .. subflt::
2513
2514       Uso máximo de memoria (MiB)
2515
2516       .. image:: plots/mem-split-1cpu.pdf
2517
2518    .. subflt::
2519
2520       *Stop-the-world* máximo (seg)
2521
2522       .. image:: plots/stw-split-1cpu.pdf
2523
2524    .. subflt::
2525
2526       Pausa real máxima (seg)
2527
2528       .. image:: plots/pause-split-1cpu.pdf
2529
2530 ``conalloc``
2531 ^^^^^^^^^^^^
2532 En la figura :vref:`fig:sol-conalloc-1cpu` se pueden observar los resultados
2533 para ``conalloc`` al utilizar un solo procesador. Los cambios con respecto
2534 a lo observado para ``concpu`` son mínimos. El efecto de la mejoría al usar
2535 marcado concurrente pero no *eager allocation* no se observa más, dado que
2536 ``conalloc`` pide memoria en todos los hilos, se crea un cuello de botella. Se
2537 ve claramente como tampoco baja la cantidad de recolecciones hecha debido
2538 a esto y se invierte la variabilidad entre los tiempos pico de pausa real
2539 y *stop-the-world* (sin una razón obvia, pero probablemente relacionado que
2540 todos los hilos piden memoria).
2541
2542 Al utilizar 4 procesadores (figura :vref:`fig:sol-conalloc-4cpu`), más allá de
2543 las diferencias mencionadas para 1 procesador, no se observan grandes cambios
2544 con respecto a lo observado para ``concpu``, excepto que los tiempos de pausa
2545 (real y *stop-the-world*) son notablemente más pequeños, lo que pareciera
2546 confirmar un error en la medición de ``concpu``.
2547
2548 ``split``
2549 ^^^^^^^^^
2550 Este es el primer caso donde se aprecia la sustancial mejora proporcionada por
2551 una pequeña optimización, el caché de ``findSize()`` (ver
2552 :ref:`sol_minor_findsize`). En la figura :vref:`fig:sol-split-1cpu` se puede
2553 observar con claridad como, para cualquier configuración de CDGC, hay una
2554 caída notable en el tiempo total de ejecución. Sin embargo, a excepción de
2555 cuando se utiliza *eager allocation*, la cantidad de recolecciones y memoria
2556 usada permanece igual.
2557
2558 La utilización de *eager allocation* mejora (aunque de forma apenas
2559 apreciable) el tiempo de ejecución, la cantidad de recolecciones baja a un
2560 tercio y el tiempo de pausa real cae dramáticamente. Al usar marcado
2561 concurrente ya se observa una caída determinante en el tiempo de
2562 *stop-the-world*. Todo esto sin verse afectado el uso máximo de memoria,
2563 incluso al usar *eager allocation*.
2564
2565 Se omiten los resultados para más de un procesador por ser prácticamente
2566 idénticos para este análisis.
2567
2568 .. raw:: latex
2569
2570    \clearpage
2571
2572 .. flt:: fig:sol-mcore-1cpu
2573
2574    Resultados para ``mcore`` (utilizando 1 procesador)
2575
2576    Resultados para ``mcore`` (utilizando 1 procesador). Se presenta el
2577    mínimos (en negro), la media centrada entre dos desvíos estándar (en gris),
2578    y el máximo (en blanco) calculados sobre 50 corridas (para tiempo de
2579    ejecución) o 20 corridas (para el resto).
2580
2581    .. subflt::
2582
2583       Tiempo de ejecución (seg)
2584
2585       .. image:: plots/time-mcore-1cpu.pdf
2586
2587    .. subflt::
2588
2589       Cantidad de recolecciones
2590
2591       .. image:: plots/ncol-mcore-1cpu.pdf
2592
2593    .. subflt::
2594
2595       Uso máximo de memoria (MiB)
2596
2597       .. image:: plots/mem-mcore-1cpu.pdf
2598
2599    .. subflt::
2600
2601       *Stop-the-world* máximo (seg)
2602
2603       .. image:: plots/stw-mcore-1cpu.pdf
2604
2605    .. subflt::
2606
2607       Pausa real máxima (seg)
2608
2609       .. image:: plots/pause-mcore-1cpu.pdf
2610
2611 .. flt:: fig:sol-mcore-4cpu
2612
2613    Resultados para ``mcore`` (utilizando 4 procesadores)
2614
2615    Resultados para ``mcore`` (utilizando 4 procesadores). Se presenta el
2616    mínimos (en negro), la media centrada entre dos desvíos estándar (en gris),
2617    y el máximo (en blanco) calculados sobre 50 corridas (para tiempo de
2618    ejecución) o 20 corridas (para el resto).
2619
2620    .. subflt::
2621
2622       Tiempo de ejecución (seg)
2623
2624       .. image:: plots/time-mcore-4cpu.pdf
2625
2626    .. subflt::
2627
2628       Cantidad de recolecciones
2629
2630       .. image:: plots/ncol-mcore-4cpu.pdf
2631
2632    .. subflt::
2633
2634       Uso máximo de memoria (MiB)
2635
2636       .. image:: plots/mem-mcore-4cpu.pdf
2637
2638    .. subflt::
2639
2640       *Stop-the-world* máximo (seg)
2641
2642       .. image:: plots/stw-mcore-4cpu.pdf
2643
2644    .. subflt::
2645
2646       Pausa real máxima (seg)
2647
2648       .. image:: plots/pause-mcore-4cpu.pdf
2649
2650 .. flt:: fig:sol-rnddata-1cpu
2651
2652    Resultados para ``rnddata`` (utilizando 1 procesador)
2653
2654    Resultados para ``rnddata`` (utilizando 1 procesador). Se presenta el
2655    mínimos (en negro), la media centrada entre dos desvíos estándar (en gris),
2656    y el máximo (en blanco) calculados sobre 50 corridas (para tiempo de
2657    ejecución) o 20 corridas (para el resto).
2658
2659    .. subflt::
2660
2661       Tiempo de ejecución (seg)
2662
2663       .. image:: plots/time-rnddata-1cpu.pdf
2664
2665    .. subflt::
2666
2667       Cantidad de recolecciones
2668
2669       .. image:: plots/ncol-rnddata-1cpu.pdf
2670
2671    .. subflt::
2672
2673       Uso máximo de memoria (MiB)
2674
2675       .. image:: plots/mem-rnddata-1cpu.pdf
2676
2677    .. subflt::
2678
2679       *Stop-the-world* máximo (seg)
2680
2681       .. image:: plots/stw-rnddata-1cpu.pdf
2682
2683    .. subflt::
2684
2685       Pausa real máxima (seg)
2686
2687       .. image:: plots/pause-rnddata-1cpu.pdf
2688
2689 ``mcore``
2690 ^^^^^^^^^
2691 El caso de ``mcore`` es interesante por ser, funcionalmente, una combinación
2692 entre ``concpu`` y ``split``, con un agregado extra: el incremento notable de
2693 la competencia por utilizar el recolector entre los múltiples hilos.
2694
2695 Los efectos observados (en la figura :vref:`fig:sol-mcore-1cpu` para
2696 1 procesador y en la figura :vref:`fig:sol-mcore-4cpu` para 4) confirman esto,
2697 al ser una suma de los efectos observados para ``concpu`` y ``split``, con el
2698 agregado de una particularidad extra por la mencionada competencia entre
2699 hilos. A diferencia de ``concpu`` donde el incremento de procesadores resulta
2700 en un decremento en el tiempo total de ejecución, en este caso resulta en una
2701 disminución, dado que se necesita mucha sincronización entre hilos, por
2702 utilizar todos de forma intensiva los servicios del recolector (y por lo tanto
2703 competir por su *lock* global).
2704
2705 Otro efecto común observado es que cuando el tiempo de pausa es muy pequeño
2706 (del orden de los milisegundos), el marcado concurrente suele incrementarlo en
2707 vez de disminuirlo.
2708
2709 ``rnddata``
2710 ^^^^^^^^^^^
2711 En la figura :vref:`fig:sol-rnddata-1cpu` se presentan los resultados para
2712 ``rnddata`` utilizando 1 procesador. Una vez más estamos ante un caso en el
2713 cual se observa claramente la mejoría gracias a una modificación en particular
2714 principalmente. En esta caso es el marcado preciso. Se puede ver claramente
2715 como mejora el tiempo de total de ejecución a algo más que la mitad (en
2716 promedio, aunque se observa una anomalía donde el tiempo baja hasta más de
2717 3 veces). Sin embargo, a menos que se utilice *eager allocation* o *early
2718 collection* (que en este caso prueba ser muy efectivo), la cantidad de
2719 recolecciones aumenta considerablemente.
2720
2721 La explicación puede ser hallada en el consumo de memoria, que baja unas
2722 3 veces en promedio usando marcado preciso que además hace disminuir
2723 drásticamente (unas 10 veces) el tiempo de pausa (real y *stop-the-world*). El
2724 tiempo de *stop-the-world* disminuye unas 10 veces más al usar marcado
2725 concurrente y el tiempo de pausa real al usar *eager allocation*, pero en este
2726 caso el consumo de memoria aumenta también bastante (aunque no tanto como
2727 disminuye el tiempo de pausa, por lo que puede ser un precio que valga la pena
2728 pagar si se necesitan tiempos de pausa muy pequeños).
2729
2730 El aumento en el variación de los tiempos de ejecución al usar marcado preciso
2731 probablemente se debe a lo siguiente: con marcado conservativo, debe estar
2732 sobreviviendo a las recolecciones el total de memoria pedida por el programa,
2733 debido a *falsos positivos* (por eso no se observa prácticamente variación en el
2734 tiempo de ejecución y memoria máxima consumida); al marcar con precisión
2735 parcial, se logra disminuir mucho la cantidad de *falsos positivos*, pero el
2736 *stack* y la memoria estática, se sigue marcado de forma conservativa, por lo
2737 tanto dependiendo de los valores (aleatorios) generados por la prueba, aumenta
2738 o disminuye la cantidad de *falsos positivos*, variando así la cantidad de
2739 memoria consumida y el tiempo de ejecución.
2740
2741 No se muestran los resultados para más de un procesador por ser demasiado
2742 similares a los obtenidos utilizando solo uno.
2743
2744 ``sbtree``
2745 ^^^^^^^^^^
2746 Los resultados para ``sbtree`` son tan similares a los obtenidos con
2747 ``bigarr`` que directamente se omiten por completo, dado que no aportan ningún
2748 tipo de información nueva. Por un lado es esperable, dado que ambas pruebas se
2749 limitan prácticamente a pedir memoria, la única diferencia es que una pide
2750 objetos grandes y otra objetos pequeños, pero esta diferencia parece no
2751 afectar la forma en la que se comportan los cambios introducidos en este
2752 trabajo.
2753
2754 .. flt:: fig:sol-bh-1cpu
2755
2756    Resultados para ``bh`` (utilizando 1 procesador)
2757
2758    Resultados para ``bh`` (utilizando 1 procesador). Se presenta el
2759    mínimos (en negro), la media centrada entre dos desvíos estándar (en gris),
2760    y el máximo (en blanco) calculados sobre 50 corridas (para tiempo de
2761    ejecución) o 20 corridas (para el resto).
2762
2763    .. subflt::
2764
2765       Tiempo de ejecución (seg)
2766
2767       .. image:: plots/time-bh-1cpu.pdf
2768
2769    .. subflt::
2770
2771       Cantidad de recolecciones
2772
2773       .. image:: plots/ncol-bh-1cpu.pdf
2774
2775    .. subflt::
2776
2777       Uso máximo de memoria (MiB)
2778
2779       .. image:: plots/mem-bh-1cpu.pdf
2780
2781    .. subflt::
2782
2783       *Stop-the-world* máximo (seg)
2784
2785       .. image:: plots/stw-bh-1cpu.pdf
2786
2787    .. subflt::
2788
2789       Pausa real máxima (seg)
2790
2791       .. image:: plots/pause-bh-1cpu.pdf
2792
2793 .. raw:: latex
2794
2795    \clearpage
2796
2797
2798 Resultados para pruebas pequeñas
2799 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2800
2801 A continuación se presentan los resultados obtenidos para las pruebas pequeñas
2802 (ver :ref:`sol_bench_small`). Se recuerda que si bien este conjunto de pruebas
2803 se compone de programas reales, que efectúan una tarea útil, están diseñados
2804 para ejercitar la asignación de memoria y que no son recomendados para evaluar
2805 el desempeño de recolectores de basura. Sin embargo se las utiliza igual por
2806 falta de programas más realistas, por lo que hay que tomarlas como un grado de
2807 suspicacia.
2808
2809 ``bh``
2810 ^^^^^^
2811 .. flt:: t:sol-prec-mem-bh
2812    :type: table
2813
2814    Memoria pedida y asignada para ``bh`` según modo de marcado
2815
2816    Memoria pedida y asignada para ``bh`` según modo de marcado conservativo
2817    o preciso (acumulativo durante toda la vida del programa).
2818
2819    ============== ============== ============== =================
2820    Memoria        Pedida (MiB)   Asignada (MiB) Desperdicio (MiB)
2821    ============== ============== ============== =================
2822    Conservativo   302.54         354.56         52.02 (15%)
2823    Preciso        302.54         472.26         169.72 (36%)
2824    ============== ============== ============== =================
2825
2826 .. flt:: fig:sol-bisort-1cpu
2827
2828    Resultados para ``bisort`` (utilizando 1 procesador)
2829
2830    Resultados para ``bisort`` (utilizando 1 procesador). Se presenta el
2831    mínimos (en negro), la media centrada entre dos desvíos estándar (en gris),
2832    y el máximo (en blanco) calculados sobre 50 corridas (para tiempo de
2833    ejecución) o 20 corridas (para el resto).
2834
2835    .. subflt::
2836
2837       Tiempo de ejecución (seg)
2838
2839       .. image:: plots/time-bisort-1cpu.pdf
2840
2841    .. subflt::
2842
2843       Cantidad de recolecciones
2844
2845       .. image:: plots/ncol-bisort-1cpu.pdf
2846
2847    .. subflt::
2848
2849       Uso máximo de memoria (MiB)
2850
2851       .. image:: plots/mem-bisort-1cpu.pdf
2852
2853    .. subflt::
2854
2855       *Stop-the-world* máximo (seg)
2856
2857       .. image:: plots/stw-bisort-1cpu.pdf
2858
2859    .. subflt::
2860
2861       Pausa real máxima (seg)
2862
2863       .. image:: plots/pause-bisort-1cpu.pdf
2864
2865 En la figura :vref:`fig:sol-bh-1cpu` se pueden observar los resultados
2866 para ``bh`` al utilizar un solo procesador. Ya en una prueba un poco más
2867 realista se puede observar el efecto positivo del marcado preciso, en especial
2868 en la cantidad de recolecciones efectuadas (aunque no se traduzca en un menor
2869 consumo de memoria).
2870
2871 Sin embargo se observa también un efecto nocivo del marcado preciso en el
2872 consumo de memoria que intuitivamente debería disminuir, pero crece, y de
2873 forma considerable (unas 3 veces en promedio). La razón de esta particularidad
2874 es el incremento en el espacio necesario para almacenar objetos debido a que
2875 el puntero a la información del tipo se guarda al final del bloque (ver
2876 :ref:`sol_precise`). En el cuadro :vref:`t:sol-prec-mem-bh` se puede observar
2877 la cantidad de memoria pedida por el programa, la cantidad de memoria
2878 realmente asignada por el recolector (y la memoria desperdiciada) cuando se
2879 usa marcado conservativo y preciso. Estos valores fueron tomados usando la
2880 opción ``malloc_stats_file`` (ver :ref:`sol_stats`).
2881
2882 Más allá de esto, los resultados son muy similares a los obtenidos para
2883 pruebas sintetizadas que se limitan a ejercitar el recolector (como ``bigarr``
2884 y ``sbtree``), lo que habla de lo mucho que también lo hace este pequeño
2885 programa.
2886
2887 No se muestran los resultados para más de un procesador por ser extremadamente
2888 similares a los obtenidos utilizando solo uno.
2889
2890 ``bisort``
2891 ^^^^^^^^^^
2892 La figura :vref:`fig:sol-bisort-1cpu` muestra los resultados para ``bisort``
2893 al utilizar 1 procesador. En este caso el parecido es con los resultados para
2894 la prueba sintetizada ``split``, con la diferencia que el tiempo de ejecución
2895 total prácticamente no varía entre TBGC y CDGC, ni entre las diferentes
2896 configuraciones del último (evidentemente en este caso no se aprovecha el
2897 caché de ``findSize()``).
2898
2899 Otra diferencia notable es la considerable reducción del tiempo de pausa real
2900 al utilizar *early collection* (más de 3 veces menor en promedio comparado
2901 a cuando se marca de forma conservativa, y más de 2 veces menor que cuando se
2902 hace de forma precisa), lo que indica que la predicción de cuando se va
2903 a necesitar una recolección es más efectiva que para ``split``.
2904
2905 No se muestran los resultados para más de un procesador por ser extremadamente
2906 similares a los obtenidos utilizando solo uno.
2907
2908 .. raw:: latex
2909
2910    \clearpage
2911
2912 .. flt:: fig:sol-em3d-1cpu
2913
2914    Resultados para ``em3d`` (utilizando 1 procesador)
2915
2916    Resultados para ``em3d`` (utilizando 1 procesador). Se presenta el
2917    mínimos (en negro), la media centrada entre dos desvíos estándar (en gris),
2918    y el máximo (en blanco) calculados sobre 50 corridas (para tiempo de
2919    ejecución) o 20 corridas (para el resto).
2920
2921    .. subflt::
2922
2923       Tiempo de ejecución (seg)
2924
2925       .. image:: plots/time-em3d-1cpu.pdf
2926
2927    .. subflt::
2928
2929       Cantidad de recolecciones
2930
2931       .. image:: plots/ncol-em3d-1cpu.pdf
2932
2933    .. subflt::
2934
2935       Uso máximo de memoria (MiB)
2936
2937       .. image:: plots/mem-em3d-1cpu.pdf
2938
2939    .. subflt::
2940
2941       *Stop-the-world* máximo (seg)
2942
2943       .. image:: plots/stw-em3d-1cpu.pdf
2944
2945    .. subflt::
2946
2947       Pausa real máxima (seg)
2948
2949       .. image:: plots/pause-em3d-1cpu.pdf
2950
2951 ``em3d``
2952 ^^^^^^^^
2953 Los resultados para ``em3d`` (figura :vref:`fig:sol-em3d-1cpu`) son
2954 sorprendentemente similares a los de ``bisort``. La única diferencia es que en
2955 este caso el marcado preciso y el uso de *early collection** no parecen
2956 ayudar; por el contrario, aumentan levemente el tiempo de pausa real.
2957
2958 Una vez más no se muestran los resultados para más de un procesador por ser
2959 extremadamente similares a los obtenidos utilizando solo uno.
2960
2961 .. flt:: fig:sol-tsp-1cpu
2962
2963    Resultados para ``tsp`` (utilizando 1 procesador)
2964
2965    Resultados para ``tsp`` (utilizando 1 procesador). Se presenta el
2966    mínimos (en negro), la media centrada entre dos desvíos estándar (en gris),
2967    y el máximo (en blanco) calculados sobre 50 corridas (para tiempo de
2968    ejecución) o 20 corridas (para el resto).
2969
2970    .. subflt::
2971
2972       Tiempo de ejecución (seg)
2973
2974       .. image:: plots/time-tsp-1cpu.pdf
2975
2976    .. subflt::
2977
2978       Cantidad de recolecciones
2979
2980       .. image:: plots/ncol-tsp-1cpu.pdf
2981
2982    .. subflt::
2983
2984       Uso máximo de memoria (MiB)
2985
2986       .. image:: plots/mem-tsp-1cpu.pdf
2987
2988    .. subflt::
2989
2990       *Stop-the-world* máximo (seg)
2991
2992       .. image:: plots/stw-tsp-1cpu.pdf
2993
2994    .. subflt::
2995
2996       Pausa real máxima (seg)
2997
2998       .. image:: plots/pause-tsp-1cpu.pdf
2999
3000 .. flt:: fig:sol-voronoi-1cpu
3001
3002    Resultados para ``voronoi`` (utilizando 1 procesador)
3003
3004    Resultados para ``voronoi`` (utilizando 1 procesador). Se presenta el
3005    mínimos (en negro), la media centrada entre dos desvíos estándar (en gris),
3006    y el máximo (en blanco) calculados sobre 50 corridas (para tiempo de
3007    ejecución) o 20 corridas (para el resto).
3008
3009    .. subflt::
3010
3011       Tiempo de ejecución (seg)
3012
3013       .. image:: plots/time-voronoi-1cpu.pdf
3014
3015    .. subflt::
3016
3017       Cantidad de recolecciones
3018
3019       .. image:: plots/ncol-voronoi-1cpu.pdf
3020
3021    .. subflt::
3022
3023       Uso máximo de memoria (MiB)
3024
3025       .. image:: plots/mem-voronoi-1cpu.pdf
3026
3027    .. subflt::
3028
3029       *Stop-the-world* máximo (seg)
3030
3031       .. image:: plots/stw-voronoi-1cpu.pdf
3032
3033    .. subflt::
3034
3035       Pausa real máxima (seg)
3036
3037       .. image:: plots/pause-voronoi-1cpu.pdf
3038
3039 .. flt:: fig:sol-voronoi-4cpu
3040
3041    Resultados para ``voronoi`` (utilizando 4 procesadores)
3042
3043    Resultados para ``voronoi`` (utilizando 4 procesadores). Se presenta el
3044    mínimos (en negro), la media centrada entre dos desvíos estándar (en gris),
3045    y el máximo (en blanco) calculados sobre 50 corridas (para tiempo de
3046    ejecución) o 20 corridas (para el resto).
3047
3048    .. subflt::
3049
3050       Tiempo de ejecución (seg)
3051
3052       .. image:: plots/time-voronoi-4cpu.pdf
3053
3054    .. subflt::
3055
3056       Cantidad de recolecciones
3057
3058       .. image:: plots/ncol-voronoi-4cpu.pdf
3059
3060    .. subflt::
3061
3062       Uso máximo de memoria (MiB)
3063
3064       .. image:: plots/mem-voronoi-4cpu.pdf
3065
3066    .. subflt::
3067
3068       *Stop-the-world* máximo (seg)
3069
3070       .. image:: plots/stw-voronoi-4cpu.pdf
3071
3072    .. subflt::
3073
3074       Pausa real máxima (seg)
3075
3076       .. image:: plots/pause-voronoi-4cpu.pdf
3077
3078 ``tsp``
3079 ^^^^^^^^
3080 Los resultados para ``tsp`` (figura :vref:`fig:sol-tsp-1cpu`) son
3081 prácticamente idénticos a los de ``bisort``. La única diferencia es que la
3082 reducción del tiempo de pausa real es un poco menor.
3083
3084 Esto confirma en cierta medida la poca utilidad de este juego de pruebas para
3085 medir el rendimiento de un recolector, dado que evidentemente, si bien todas
3086 resuelven problemas diferentes, realizan todas el mismo tipo de trabajo.
3087
3088 Una vez más no se muestran los resultados para más de un procesador por ser
3089 extremadamente similares a los obtenidos utilizando solo uno.
3090
3091 ``voronoi``
3092 ^^^^^^^^^^^
3093 En la figura :vref:`fig:sol-voronoi-1cpu` se presentan los resultados para
3094 ``voronoi``, probablemente la prueba más interesante de este conjunto de
3095 pruebas pequeñas.
3096
3097 Por un lado se puede observar una vez más como baja dramáticamente el tiempo
3098 total de ejecución cuando se empieza a utilizar CDGC. Ya se ha visto que esto
3099 es común en programas que se benefician del caché de ``findSize()``, pero en
3100 este caso no parece provenir toda la ganancia solo de ese cambio, dado que
3101 para TBGC se ve una variación entre los resultados muy grande que desaparece
3102 al cambiar a CDGC, esto no puede ser explicado por esa optimización. En
3103 general la disminución de la variación de los resultados hemos visto que está
3104 asociada al incremento en la precisión en el marcado, dado que los *falsos
3105 positivos* ponen una cuota de aleatoriedad importante. Pero este tampoco
3106 parece ser el caso, ya que no se observan cambios apreciables al pasar a usar
3107 marcado preciso.
3108
3109 Lo que se observa en esta oportunidad es un caso patológico de un mal factor
3110 de ocupación del *heap* (ver :ref:`sol_ocup`). Lo que muy probablemente está
3111 sucediendo con TBGC es que luego de ejecutar una recolección, se libera muy
3112 poco espacio, entonces luego de un par de asignaciones, es necesaria una nueva
3113 recolección. En este caso es donde dificulta la tarea de analizar los
3114 resultados la falta de métricas para TBGC, dado que no se pueden observar la
3115 cantidad de recolecciones ni de consumo máximo de memoria. Sin embargo es
3116 fácil corroborar esta teoría experimentalmente, gracias a la opción
3117 ``min_free``. Utilizando la ``min_free=0`` para emular el comportamiento de
3118 TBGC (se recuerda que el valor por omisión es ``min_free=5``), se obtiene una
3119 media de 4 segundos, mucho más parecida a lo obtenido para TBGC.
3120
3121 Otra particularidad de esta prueba es que al utilizar *early collection* el
3122 tiempo de pausa real aumenta notablemente al usar un procesador, mientras que
3123 al usar 4 (ver figura :vref:`fig:sol-voronoi-4cpu` disminuye levemente (además
3124 de otros cambios en el nivel de variación, pero en general las medias no
3125 cambian).
3126
3127 Resultados para pruebas reales
3128 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3129
3130 .. flt:: fig:sol-dil-1cpu
3131
3132    Resultados para ``dil`` (utilizando 1 procesador)
3133
3134    Resultados para ``dil`` (utilizando 1 procesador). Se presenta el
3135    mínimos (en negro), la media centrada entre dos desvíos estándar (en gris),
3136    y el máximo (en blanco) calculados sobre 50 corridas (para tiempo de
3137    ejecución) o 20 corridas (para el resto).
3138
3139    .. subflt::
3140
3141       Tiempo de ejecución (seg)
3142
3143       .. image:: plots/time-dil-1cpu.pdf
3144
3145    .. subflt::
3146
3147       Cantidad de recolecciones
3148
3149       .. image:: plots/ncol-dil-1cpu.pdf
3150
3151    .. subflt::
3152
3153       Uso máximo de memoria (MiB)
3154
3155       .. image:: plots/mem-dil-1cpu.pdf
3156
3157    .. subflt::
3158
3159       *Stop-the-world* máximo (seg)
3160
3161       .. image:: plots/stw-dil-1cpu.pdf
3162
3163    .. subflt::
3164
3165       Pausa real máxima (seg)
3166
3167       .. image:: plots/pause-dil-1cpu.pdf
3168
3169 A continuación se presentan los resultados obtenidos para las pruebas reales
3170 (ver :ref:`sol_bench_real`). Recordamos que solo se pudo halla un programa que
3171 pueda ser utilizado a este fin, Dil_, y que el objetivo principal de este
3172 trabajo se centra alrededor de obtener resultados positivos para este
3173 programa, por lo que a pesar de ser una única prueba, se le presta particular
3174 atención.
3175
3176 ``dil``
3177 ^^^^^^^
3178 En la figura :vref:`fig:sol-dil-1cpu` se presentan los resultados para
3179 ``dil`` al utilizar un procesador. Una vez más vemos una mejoría inmediata del
3180 tiempo total de ejecución al pasar de TBGC a CDGC, y una vez más se debe
3181 principalmente al mal factor de ocupación del *heap* de TBGC, dado que
3182 utilizando CDGC con la opción ``min_free=0`` se obtiene una media del orden de
3183 los 80 segundos, bastante más alta que el tiempo obtenido para TBGC.
3184
3185 .. flt:: fig:sol-dil-4cpu
3186    :placement: t
3187
3188    Resultados para ``dil`` (utilizando 4 procesadores)
3189
3190    Resultados para ``dil`` (utilizando 4 procesadores). Se presenta el
3191    mínimos (en negro), la media centrada entre dos desvíos estándar (en gris),
3192    y el máximo (en blanco) calculados sobre 50 corridas (para tiempo de
3193    ejecución) o 20 corridas (para el resto).
3194
3195    .. subflt::
3196
3197       Tiempo de ejecución (seg)
3198
3199       .. image:: plots/time-dil-4cpu.pdf
3200
3201    .. subflt::
3202
3203       Cantidad de recolecciones
3204
3205       .. image:: plots/ncol-dil-4cpu.pdf
3206
3207    .. subflt::
3208
3209       Uso máximo de memoria (MiB)
3210
3211       .. image:: plots/mem-dil-4cpu.pdf
3212
3213    .. subflt::
3214
3215       *Stop-the-world* máximo (seg)
3216
3217       .. image:: plots/stw-dil-4cpu.pdf
3218
3219    .. subflt::
3220
3221       Pausa real máxima (seg)
3222
3223       .. image:: plots/pause-dil-4cpu.pdf
3224
3225 Sin embargo se observa un pequeño incremento del tiempo de ejecución al
3226 introducir marcado preciso, y un incremento bastante más importante (de
3227 alrededor del 30%) en el consumo máximo de memoria. Nuevamente, como pasa con
3228 la prueba ``bh``, el efecto es probablemente producto del incremento en el
3229 espacio necesario para almacenar objetos debido a que el puntero a la
3230 información del tipo se guarda al final del bloque (ver :ref:`sol_precise`).
3231 En el cuadro :vref:`t:sol-prec-mem-dil` se puede observar la diferencia de
3232 memoria desperdiciada entre el modo conservativo y preciso.
3233
3234 .. flt:: t:sol-prec-mem-dil
3235    :type: table
3236    :placement: b
3237
3238    Memoria pedida y asignada para ``dil`` según modo de marcado
3239
3240    Memoria pedida y asignada para ``dil`` según modo de marcado conservativo
3241    o preciso (acumulativo durante toda la vida del programa).
3242
3243    ============== ============== ============== =================
3244    Memoria        Pedida (MiB)   Asignada (MiB) Desperdicio (MiB)
3245    ============== ============== ============== =================
3246    Conservativo   307.48         399.94         92.46 (23%)
3247    Preciso        307.48         460.24         152.76 (33%)
3248    ============== ============== ============== =================
3249
3250 El pequeño incremento en el tiempo total de ejecución podría estar dado por la
3251 mayor probabilidad de tener *falsos positivos* debido al incremento del tamaño
3252 del *heap*; se recuerda que el *stack* y memoria estática se siguen marcado de
3253 forma conservativa, incluso en modo preciso.
3254
3255 También se puede observar una gran disminución del tiempo total de ejecución
3256 (cerca de un 60%, y más de un 200% comparado con TBGC) alrededor de la mitad)
3257 al empezar a usar *eager allocation*, acompañado como es usual de una baja en
3258 la cantidad de recolecciones realizadas (esta vez mayor, de más de 3 veces)
3259 y de una caída drástica del tiempo de pausa real (alrededor de 40 veces más
3260 pequeño); todo esto con un incremento marginal en el consumo total de memoria
3261 (aproximadamente un 5%). En este caso el uso de *early collection* apenas
3262 ayuda a bajar el tiempo de pausa real en un 20% en promedio aproximadamente.
3263 El tiempo de *stop-the-world* cae dramáticamente al empezar a realizar la fase
3264 de marcado de manera concurrente; es 200 veces más pequeño.
3265
3266 Al utilizar 4 procesadores (ver figura :vref:`fig:sol-dil-4cpu`), hay algunos
3267 pequeños cambios. El tiempo total de ejecución es reducido todavía más (un 20%
3268 que cuando se usa 1 procesador) cuando se utiliza *eager allocation*. Además
3269 al utilizar *early collection*, hay otra pequeña ganancia de alrededor del
3270 10%, tanto para el tiempo total de ejecución como para el tiempo de pausa
3271 real.
3272
3273
3274 .. _sol_accept:
3275
3276 Aceptación
3277 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3278
3279 Los avances de este trabajo fueron comunicados regularmente a la comunidad de
3280 D_ a través de un blog [LMTDGC]_ y del grupo de noticias de D_. Los
3281 comentarios hechos sobre el primero son en general positivos y denotan una
3282 buena recepción por parte de la comunidad a las modificaciones propuestas.
3283
3284 Una vez agregado el marcado concurrente se hace un anuncio en el grupo de
3285 noticias que también muestra buenos comentarios y aceptación, en particular
3286 por parte de Sean Kelly, encargado de mantener el *runtime* de `D 2.0`_, que
3287 comienza a trabajar en adaptar el recolector con idea de tal vez incluirlo en
3288 el futuro [NGA19235]_. Poco después Sean Kelly publica una versión preliminar
3289 de la adaptación en la lista de correos que coordina el desarrollo del
3290 *runtime* de `D 2.0`_ [DRT117]_.
3291
3292 También se ha mostrado interés de incluirlo en Tango_, aunque no se han ha
3293 comenzado aún con la adaptación, pero debería ser trivial dado que este
3294 trabajo se desarrolla usando Tango_ (y el recolector está basado en el de
3295 Tango_) [TT1997]_.
3296
3297
3298 .. include:: links.rst
3299
3300 .. vim: set ts=3 sts=3 sw=3 et tw=78 spelllang=es :