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