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