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