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