/
gc.xml
688 lines (671 loc) · 27.2 KB
/
gc.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
<?xml version="1.0" encoding="utf-8"?>
<!-- $Revision$ -->
<!-- EN-Revision: 04f4545c02b4643283da216e6b62e39a307a9a9a Maintainer: seros Status: ready -->
<!-- Reviewed: no Maintainer: seros -->
<chapter xml:id="features.gc" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Recolección de basura</title>
<para>
Esta sección explica las ventajas del nuevo mecanismo de Recoleción de basura (GC por sus siglas
en inglés de «Garbage Collection») de la versión 5.3 de PHP.
</para>
<sect1 xml:id="features.gc.refcounting-basics">
<title>Introducción al contador de referencias</title>
<para>
Una variable en PHP se almacena en un contenedor llamado "zval". Un contenedor zval
contiene, además del tipo de la variable y su valor, dos bits adicionales de
información. Al primero se le llama "is_ref" y contiene un valor booleano
que indica si la variable es parte o no de un "conjunto de referencias". Con
este bit, el motor de PHP sabe diferenciar entre variables normales
y referencias. Puesto que PHP permite referencias definidas por el usuario, tal y
como se crean con el operador &, un contenedor zval tiene también un mecanismo
contador de referencias para optimizar el uso de memoria. Esta segunda pieza adicional de
información, llamada "refcount", contiene el número de variables (también llamadas
símbolos) que apuntan a este contenedor zval. Todos los símbolos se almacenan en
una tabla de símbolos, de las cuales hay una por cada ámbito. Hay un ámbito para el
script principal (es decir, el solicitado por el navegador), además de uno por
cada función o método.
</para>
<para>
Se crea un contenedor zval al crear una nueva variable con un valor constante,
como por ejemplo:
<example>
<title>Creación de un nuevo contenedor zval</title>
<programlisting role="php">
<![CDATA[
<?php
$a = "nuevo string";
?>
]]>
</programlisting>
</example>
</para>
<para>
En este caso, el nombre del nuevo símbolo, <literal>a</literal>, se crea en el ámbito actual
y se crea un nuevo contenedor de variable con el tipo <type>string</type> y el valor
<literal>nuevo string</literal>. El bit "is_ref" se establece por omisión a &false; dado que no se
ha creado ninguna referencia en el espacio del usuario. "refcount" se establece a <literal>1</literal>, pues
solo hay un símbolo que haga uso de este contenedor de variable. Tenga en cuenta
que si "refcount" es <literal>1</literal>, "is_ref" siempre valdrá &false;. Si tiene <link
xlink:href="&url.xdebug;">Xdebug</link> instalado, puede mostrar esta
información llamando a <function>xdebug_debug_zval</function>.
</para>
<para>
<example>
<title>Mostrar información de zval</title>
<programlisting role="php">
<![CDATA[
<?php
$a = "nuevo string";
xdebug_debug_zval('a');
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
a: (refcount=1, is_ref=0)='nuevo string'
]]>
</screen>
</example>
</para>
<para>
Al asignar esta variable a otro nombre de variable, se incrementará refcount.
</para>
<para>
<example>
<title>Incremento de refcount de un zval</title>
<programlisting role="php">
<![CDATA[
<?php
$a = "nuevo string";
$b = $a;
xdebug_debug_zval( 'a' );
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
a: (refcount=2, is_ref=0)='nuevo string'
]]>
</screen>
</example>
</para>
<para>
Aquí, refcount vale <literal>2</literal>, pues el mismo contenedor de variable está
vinculado tanto por <varname>a</varname> como por <varname>b</varname>.
PHP es lo suficiente inteligente para no copiar el contenedor de variable
en sí cuando no es necesario. Los contenedores de variables se destruyen cuando
"refcount" llega a cero. "refcount" se decrementa en uno cuando alguno de los
símbolos vinculados al contenedor de variable abandona su ámbito (p.ej. cuando
finaliza una función) o cuando un símbolo es desasignado (p. ej., llamando a <function>unset</function>).
El siguiente ejemplo muestra esto:
</para>
<para>
<example>
<title>Decremento de refcount de zval</title>
<programlisting role="php">
<![CDATA[
<?php
$a = "nuevo string";
$c = $b = $a;
xdebug_debug_zval( 'a' );
$b = 42;
xdebug_debug_zval( 'a' );
unset( $c );
xdebug_debug_zval( 'a' );
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
a: (refcount=3, is_ref=0)='nuevo string'
a: (refcount=2, is_ref=0)='nuevo string'
a: (refcount=1, is_ref=0)='nuevo string'
]]>
</screen>
</example>
</para>
<para>
Si ahora llamáramos a <literal>unset($a);</literal>, el contenedor de variable, incluyendo tanto
el tipo como el valor, se eliminarían de la memoría.
</para>
<sect2 xml:id="features.gc.compound-types">
<title>Tipos compuestos</title>
<para>
Las cosas se complican con tipos compuestos tales como <type>array</type>s y
<type>object</type>. En lugar de un valor de tipo <type>scalar</type>, los <type>array</type>s
y <type>object</type>s almacenan sus
propiedades en su propia tabla de símbolos. Esto significa que el siguiente
ejemplo crea tres contenedores zval:
</para>
<para>
<example>
<title>Crear un zval de tipo <type>array</type></title>
<programlisting role="php">
<![CDATA[
<?php
$a = array( 'meaning' => 'life', 'number' => 42 );
xdebug_debug_zval( 'a' );
?>
]]>
</programlisting>
&example.outputs.similar;
<screen>
<![CDATA[
a: (refcount=1, is_ref=0)=array (
'meaning' => (refcount=1, is_ref=0)='life',
'number' => (refcount=1, is_ref=0)=42
)
]]>
</screen>
<para>Gráficamente</para>
<mediaobject>
<alt>Los zval de un array simple</alt>
<imageobject>
<imagedata fileref="en/features/figures/simple-array.png" format="PNG"/>
</imageobject>
</mediaobject>
</example>
</para>
<para>
Los tres contenedores zval son: <varname>a</varname>, <varname>meaning</varname>, y <varname>number</varname>.
Se aplican reglas similares a la hora de incrementar y decrementar "refcounts". Abajo, añadimos otro
elemento al array, y fijamos su valor al contenido de un elemento ya
existente:
</para>
<para>
<example>
<title>Añadir un elemento existente a un array</title>
<programlisting role="php">
<![CDATA[
<?php
$a = array( 'meaning' => 'life', 'number' => 42 );
$a['life'] = $a['meaning'];
xdebug_debug_zval( 'a' );
?>
]]>
</programlisting>
&example.outputs.similar;
<screen>
<![CDATA[
a: (refcount=1, is_ref=0)=array (
'meaning' => (refcount=2, is_ref=0)='life',
'number' => (refcount=1, is_ref=0)=42,
'life' => (refcount=2, is_ref=0)='life'
)
]]>
</screen>
<para>Gráficamente</para>
<mediaobject>
<alt>Los zval de un array simple con una referencia</alt>
<imageobject>
<imagedata fileref="en/features/figures/simple-array2.png" format="PNG"/>
</imageobject>
</mediaobject>
</example>
</para>
<para>
A partir de la salida de Xdebug, vemos que tanto el antiguo como el nuevo elemento
del array apuntan a un contenedor zval cuyo "refcount" vale
<literal>2</literal>. Pese a que Xdebug muestra dos contenedores zval
con valor <literal>'life'</literal>, son el mismo. La función
<function>xdebug_debug_zval</function> no muestra esto, aunque
podria comprobarse mostrando también el puntero de memoria.
</para>
<para>
Eliminar un elemento del array es como eliminar un símbolo de un ámbito.
Al hacerlo, el "refcount" del contenedor al que apunta el elemento del array
se decrementa. De nuevo, cuando "refcount" alcanza cero, el contenedor
de la variable se elimina de la memoria. Un ejemplo que muestra esto:
</para>
<para>
<example>
<title>Eliminar un elemento de un array</title>
<programlisting role="php">
<![CDATA[
<?php
$a = array( 'meaning' => 'life', 'number' => 42 );
$a['life'] = $a['meaning'];
unset( $a['meaning'], $a['number'] );
xdebug_debug_zval( 'a' );
?>
]]>
</programlisting>
&example.outputs.similar;
<screen>
<![CDATA[
a: (refcount=1, is_ref=0)=array (
'life' => (refcount=1, is_ref=0)='life'
)
]]>
</screen>
</example>
</para>
<para>
Ahora, las cosas se vuelven interesantes si añadimos al propio array como
elemento del array, como veremos en el siguiente ejemplo, en el que también
usaremos el operador de referencia, ya que de lo contrario PHP crearía una copia:
</para>
<para>
<example>
<title>Añadir el propio array como elemento de sí mismo</title>
<programlisting role="php">
<![CDATA[
<?php
$a = array( 'one' );
$a[] =& $a;
xdebug_debug_zval( 'a' );
?>
]]>
</programlisting>
&example.outputs.similar;
<screen>
<![CDATA[
a: (refcount=2, is_ref=1)=array (
0 => (refcount=1, is_ref=0)='one',
1 => (refcount=2, is_ref=1)=...
)
]]>
</screen>
<para>Gráficamente</para>
<mediaobject>
<alt>Los zval para un array que contiene una referencia circular</alt>
<imageobject>
<imagedata fileref="en/features/figures/loop-array.png" format="PNG"/>
</imageobject>
</mediaobject>
</example>
</para>
<para>
Puede verse que tanto la variable de tipo array (<varname>a</varname>) como el segundo elemento
(<varname>1</varname>) apuntan ahora a un contenedor de variable que tiene un "refcount" de
<literal>2</literal>. Los "..." mostrados arriba indican que hay una referencia cíclica, lo cual,
por supuesto, significa que en este caso los "..." apuntan al array
original.
</para>
<para>
Al igual que antes, al eliminar una variable se elimina el símbolo y
el contador de referencias del contenedor de variable al que apunte se decrementa
en uno. De modo que, si eliminamos la variable <varname>$a</varname> después de ejecutar el código
anterior, el contador de referencias del contenedor de variable al que apuntan tanto <varname>$a</varname>
como el elemento "1" se decrementa en uno, de "2" a "1". Se puede representar así:
</para>
<para>
<example>
<title>Eliminar <varname>$a</varname></title>
<screen>
<![CDATA[
(refcount=1, is_ref=1)=array (
0 => (refcount=1, is_ref=0)='one',
1 => (refcount=1, is_ref=1)=...
)
]]>
</screen>
<para>Gráficamente</para>
<mediaobject>
<alt>Los zval después de eliminar un array con referencia circular mostrando la fuga de memoria</alt>
<imageobject>
<imagedata fileref="en/features/figures/leak-array.png" format="PNG"/>
</imageobject>
</mediaobject>
</example>
</para>
</sect2>
<sect2 xml:id="features.gc.cleanup-problems">
<title>Problemas de limpieza</title>
<para>
Pese a que ya no hay ningún símbolo en ningún ámbito que apunte a esta
estructura, esta no se puede limpiar ya que el elemento "1" del array todavía
apunta al mismo array. Al no haber un símbolo externo que apunte a
él, no hay forma por la que el usuario pueda eliminar esta estructura; por
tanto tenemos una fuga de memoria. Afortunadamente, PHP limpiará esta estructura de datos al
finalizar la petición, aunque hasta entonces esté ocupando un valioso espacio de
memoria. Esta situación ocurre a menudo si se está implementando un algoritmo
de análisis o en otras situaciones en las que un nodo hijo apunte de nuevo a un elemento
"padre". Por supuesto, esta situación también puede suceder con objetos, donde
es más frecuente que ocurra, ya que los objetos siempre se usan implícitamente por
referencia.
</para>
<para>
Esto no debería ser un problema si sólo ocurre una o dos veces, pero si
estas fugas de memoria suceden miles, o incluso millones de veces, lógicamente
esto comenzaría a ser un problema. Es especialmente problemático en scripts
de larga duración, tales como demonios donde básicamente nunca terminan las peticiones,
o en un gran conjunto de pruebas unitarias. Esto último causó problemas al
ejecutar las pruebas unitarias del componente Template de la biblioteca
eZ Componentes. En algunos casos, podrían ser necesarios 2 GB de memoria que quizás no los
tenga el servidor de pruebas.
</para>
</sect2>
</sect1>
<sect1 xml:id="features.gc.collecting-cycles">
<title>Recolección de referencias cíclicas</title>
<para>
Tradicionalmente, los mecanismos que contabilizan las referencias en memoria, tal como el que empleaba
PHP anteriormente, fallaban al abordar las fugas de memoria de referencias cíclicas.
Sin embargo, desde PHP 5.3.0 se implementa el algoritmo síncrono del artículo
<link xlink:href="&url.gc-paper;">Recolección de ciclos concurrentes en sistemas de contabilidad de referencias</link>
que resuelve este asunto.
</para>
<para>
Una explicación detallada del funcionamiento del algoritmo queda más allá del
objetivo de esta sección, aunque aquí explicaremos el mecanismo básico. Antes de nada,
debemos establecer unas reglas básicas. Si se incrementa un refcount,
este aún sigue en uso: no es basura. Si se decrementa el refcount y llega
a cero, el zval puede liberarse. Esto significa que la recolección de ciclos sólo
puede llevarse a cabo cuando un argumento refcount se decrementa a un valor que no sea cero.
En segundo lugar, en un ciclo de recolección de basura, es posible averiguar qué partes son
basura comprobando si se puede decrementar en uno sus refcount, para
después comprobar cuáles zval poseen un refcount de cero.
</para>
<para>
<mediaobject>
<alt>Algoritmo de recolección de basura</alt>
<imageobject>
<imagedata fileref="en/features/figures/gc-algorithm.png" format="PNG"/>
</imageobject>
</mediaobject>
</para>
<para>
Para evitar llamar a la comprobación de ciclos de basura en cada posible
decremento de un refcount, el algoritmo lo que hace es pasar todas las posibles raíces
(zvals) al "buffer raíz" (marcándolos en "púrpura"). También se asegura
de que cada raíz de basura sólo finaliza una vez en el buffer. Únicamente cuando el
buffer raíz está completo, comienza el mecanismo de recolección para todos los zval
diferentes que haya en su interior. Véase el paso A en la figura anterior.
</para>
<para>
En el paso B, el algoritmo inicia una primera búsqueda en profundidad de todas las posibles raíces
en las que decrementa por uno los refcount de los zval que encuentra, asegurándose de que no
decrementa dos veces el mismo zval (marcándolo en "gris"). En
el paso C, el algoritmo vuelve a llevar a cabo una búsqueda en profundidad dentro de cada nodo raíz,
para volver a comprobar el refcount de cada zval. Si ve que el refcount es
cero, se marca al zval en "blanco" (azul en la figura). Si es mayor que
cero, deshace el decremento con una búsqueda en profundidad partiendo de
ese punto, y se le vuelve a marcar en "negro". En el último
paso (D), el algoritmo recorre el buffer raíz eliminando las raíces zval que haya,
y a la vez, comprueba qué zvals se han marcado en "blanco" en
el paso anterior. Todos los zval marcados en "blanco" se eliminarán.
</para>
<para>
Ahora que ya tiene un conocimiento básico de cómo funciona el algoritmo,
volveremos atrás para ver cómo se integra esto en PHP. Por omisión, el recolector
de basuras de PHP está habilitado. Hay, sin embargo, una directiva en &php.ini;
que permite cambiar esto:
<link linkend="ini.zend.enable-gc">zend.enable_gc</link>.
</para>
<para>
Cuando el recolector de basura está habilitado, el algoritmo que busca ciclos,
tal y como se ha descrito arriba, se ejecuta cada vez que se llena el buffer raíz. Éste
tiene un tamaño fijo de 10.000 raíces posibles (se puede modificar
esto cambiando la contante <literal>GC_ROOT_BUFFER_MAX_ENTRIES</literal> en
<literal>Zend/zend_gc.c</literal> del código fuente de PHP, y recompilando
PHP). Cuando el recolector de basuras se deshabilita, no se ejecutará
el algoritmo que busca ciclos. Sea como fuere, las posibles raíces seguirían
registrándose en el buffer raíz, sin importar si el mecanismo recolector de basuras está
habilitado en la configuración o no.
</para>
<para>
Si estando deshabilitado el mecanismo recolector de basuras se llenara el buffer raíz
de posibles raíces, no se registraría al resto de raíces posibles, por lo que
no llegarían a ser analizadas por el algoritmo. Si fueran parte de un ciclo de
referencia circular, nunca se liberarían y podrían provocar una fuga de memoria.
</para>
<para>
La razón por la que se registran las posibles raíces estando deshabilitado
el mecanismo es porque es más rápido registrarlas que
comprobar en cada una de ellas si el mecanismo está habilitado.
Sin embargo, el recolector de basuras y el propio mecanismo de análisis, sí
puede conllevar una cantidad de tiempo considerable.
</para>
<para>
Ademas de poder cambiar la configuración <link linkend="ini.zend.enable-gc">zend.enable_gc</link>,
también es posible habilitar o deshabilitar el mecanismo recolector de basura
llamando a <function>gc_enable</function> o
<function>gc_disable</function> respectivamente. La llamada a estas funciones tiene
el mismo efecto que habilitar o deshabilitar el mecanismo en la propia configuración.
También es posible forzar la recolección de ciclos incluso sin que esté lleno el
buffer raíz. Para hacer esto, se puede usar la función
<function>gc_collect_cycles</function>. Esta función devuelve el número
de ciclos que fueron recolectados por el algoritmo.
</para>
<para>
El motivo por el que es posible habilitar o deshabilitar el mecanismo, o
iniciar los ciclos de recolección a mano, es porque podría haber determinadas
partes de una aplicación que necesiten mucha precisión de tiempo. En estos casos, quizás
no se desee que funcione el mecanismo recolector de basuras. Por supuesto, al deshabilitar
el recolector de basuras en algunas partes del código, se corre el riesgo de
provocar fugas de memoria, ya que algunas raíces podrían no caber en el buffer raíz.
Por tanto, lo mas prudente sería llamar a
<function>gc_collect_cycles</function> justo después de llamar a
<function>gc_disable</function> para que libere la memoria ocupada por
posibles raíces ya registradas en el buffer raíz. Esto deja por tanto
un buffer vacío, de modo que queda más espacio para almacenar
posibles raíces en el tiempo en que el mecanismo recolector de ciclos está deshabilitado.
</para>
</sect1>
<sect1 xml:id="features.gc.performance-considerations">
<title>Consideraciones acerca del Rendimiento</title>
<para>
Como mencionamos en la sección anterior, la recolección de raíces tiene muy
bajo impacto en el rendimiento, pero aquí es cuando comparamos PHP 5.2
contra PHP 5.3. Si bien la recolección de posibles raíces
comparado con la no recolección, como en PHP 5.2, es más lenta, hay otras
modificaciones en tiempo de ejecución en PHP 5.3 que impiden que esta pérdida
de rendimiento en particular pueda siquiera apreciarse.
</para>
<para>
Hay dos principales sectores en los que el rendimiento se ve afectado. El primero
es el uso reducido de memoria, y mientras que el segunda es la reducción en tiempo de ejecución
cuando el mecanismo recolector de basura lleva a cabo la limpieza de memoria. Revisaremos
estos dos asuntos.
</para>
<sect2 xml:id="features.gc.performance-considerations.reduced-mem">
<title>Uso Reducido de Memoria</title>
<para>
Antes de nada, la razón por la que se implementa el mecanismo recolector
de basuras es para reducir el uso de memoria limpiando, una vez que se cumplen
las condiciones, las variables de referencias circulares. En la implementación
de PHP, esto sucede cuando el buffer raíz está lleno, o cuando se invoca
la función <function>gc_collect_cycles</function>. En el gráfico mostrado abajo,
se muestra el uso de memoria tanto en PHP 5.2 como en 5.3, excluyendo
la memoria base que el propio PHP ocupa al arrancar.
</para>
<para>
<example>
<title>Ejemplo de uso de memoria</title>
<programlisting role="php">
<![CDATA[
<?php
class Foo
{
public $var = '3.14159265359';
}
$baseMemory = memory_get_usage();
for ( $i = 0; $i <= 100000; $i++ )
{
$a = new Foo;
$a->self = $a;
if ( $i % 500 === 0 )
{
echo sprintf( '%8d: ', $i ), memory_get_usage() - $baseMemory, "\n";
}
}
?>
]]>
</programlisting>
<mediaobject>
<alt>Comparación de uso de memoria entre PHP 5.2 y PHP 5.3</alt>
<imageobject>
<imagedata fileref="en/features/figures/gc-benchmark.png" format="PNG"/>
</imageobject>
</mediaobject>
</example>
</para>
<para>
En este ejemplo didáctico, estamos creando un objeto en el que una propiedad
enlaza de nuevo al propio objeto. Cuando la variable <varname>$a</varname>
del script se reasigna en la siguiente iteración del bucle, típicamente ocurriría
una fuga de memoria. En este caso, se fugan dos contenedores zval
(el zval del objeto, y el zval de la propiead), pero sólo se encuentra una
posible raíz: la variable que se desasignó. Tras 10.000 iteraciones, el buffer
se llena (con un total de 10.000 posibles raíces), y se lanza el mecanismo
recolector de basura y libera la memoria asociada con esas posibles raíces.
Puede apreciarse claramente en el uso de memoria "dentado" de la gráfica para
PHP 5.3. Tras las 10.000 iteraciones, el mecanismo libera la memoria asociada
a las variables con referencias cíclicas.
En este ejemplo, el propio mecanismo no debe hacer un gran trabajo, puesto
que la estructura que produce la fuga es extremadamente sencilla. A partir del
diagrama, se puede comprobar que el uso máximo de memoria en PHP 5.3 es en torno a
9 Mb, mientras que en PHP 5.2 el uso de memoria no para de aumentar.
</para>
</sect2>
<sect2 xml:id="features.gc.performance-considerations.slowdowns">
<title>Reducción en Tiempo de Ejecución</title>
<para>
El segundo sector en el que el mecanismo recolector de basura
influye en el rendimiento es en el tiempo que lleva a éste
liberar la memoria "fugada". Para comprobar de cuánto estamos hablando,
modificaremos ligeramente el script anterior para tener en cuenta un mayor número
de iteraciones, y eliminaremos las figuras de uso de memoria intermedio.
Este es el segundo script:
</para>
<para>
<example>
<title>Influencia en rendimiento de Recolector de Basuras</title>
<programlisting role="php">
<![CDATA[
<?php
class Foo
{
public $var = '3.14159265359';
}
for ( $i = 0; $i <= 1000000; $i++ )
{
$a = new Foo;
$a->self = $a;
}
echo memory_get_peak_usage(), "\n";
?>
]]>
</programlisting>
</example>
</para>
<para>
Ejecutaremos dos veces este script, una con el ajuste
<link linkend="ini.zend.enable-gc">zend.enable_gc</link> habilitado, y en la otra
deshabilitado:
</para>
<para>
<example>
<title>Ejecutando el script anterior</title>
<programlisting role="shell">
<![CDATA[
time php -dzend.enable_gc=0 -dmemory_limit=-1 -n example2.php
# and
time php -dzend.enable_gc=1 -dmemory_limit=-1 -n example2.php
]]>
</programlisting>
</example>
</para>
<para>
En la máquina de ejemplo, el primer comando parece llevar en torno a
10,7 segundos, mientras que al segundo comando le lleva 11,4. Esto es
un incremento de en torno al 7%. Sin embargo, el uso máximo de memoria
del script se ha reducido en un 98%, pasando de 931Mb a 10Mb. Esta
prueba no es muy científica, ni siquiera representativa de aplicaciones
reales, pero demuestra que el uso de memoria se beneficia del mecanismo
recolector de basuras. Lo interesante es que para este script en particular
el incremento es siempre del 7%, mientras que el ahorro de memoria
aumenta a medida que se encuentran más referencias cíclicas en
la ejecución del script.
</para>
</sect2>
<sect2 xml:id="features.gc.performance-considerations.internal-stats">
<title>Estadísticas Internas de PHP del Recolector de Basuras</title>
<para>
Todavía es posible dar más información sobre cómo funciona el mecanismo
recolector de basuras dentro de PHP. Pero para hacerlo, será necesario
recompilar PHP para habilitar el código de análises y de recopilación
de datos. Se tendrá que asignar a la variable de entorno <literal>CFLAGS</literal>
el valor <literal>-DGC_BENCH=1</literal> antes de ejecutar
<literal>./configure</literal> con las opciones deseadas. La siguiente
secuencia muestra cómo hacerlo:
</para>
<para>
<example>
<title>Recompilando PHP para habilitar el análisis del Recolector de Basuras</title>
<programlisting role="shell">
<![CDATA[
export CFLAGS=-DGC_BENCH=1
./config.nice
make clean
make
]]>
</programlisting>
</example>
</para>
<para>
Al ejecutar el ejemplo que vimos arriba con el nuevo binario de PHP
que hemos creado, veremos que se muestra el siguiente resultado tras la ejecución
de PHP:
</para>
<para>
<example>
<title>Estadísticas de Recolección de Basuras</title>
<programlisting role="shell">
<![CDATA[
GC Statistics
-------------
Runs: 110
Collected: 2072204
Root buffer length: 0
Root buffer peak: 10000
Possible Remove from Marked
Root Buffered buffer grey
-------- -------- ----------- ------
ZVAL 7175487 1491291 1241690 3611871
ZOBJ 28506264 1527980 677581 1025731
]]>
</programlisting>
</example>
</para>
<para>
Las estadísticas más informativas son las que se muestran en el primer bloque. Puede
comprobarse que el mecanismo recolector de basuras se ejecutó 110 veces, y
en total, se liberaron más de 2 millones de ubicaciones en memoria durante
esas 110 ejecuciones. Puesto que el mecanismo recolector de ciclos se ha ejecutado
al menos una vez, el "pico del buffer raíz" es siempre 10.000.
</para>
</sect2>
<sect2 xml:id="features.gc.performance-considerations.conclusion">
<title>Conclusión</title>
<para>
En general el recolector de basuras de PHP sólo provocará un retraso
cuando el algoritmo recolector de ciclos funcione, mientras que en scripts
normales (más pequeños) no habrá un impacto real en el rendimiento.
</para>
<para>
Sin embargo, en el caso en el que el mecanismo recolector de ciclos
se ejecute para scripts normales, la reducción de memoria permitirá
que puedan funcionar más scripts concurrentemente en el servidor, ya que
en total no utilizarán mucha memoria.
</para>
<para>
Los beneficios son más evidentss en scripts de larga duración, tales
como grandes suits de pruebas o scripts demonios. También en las aplicaciones
<link xlink:href="&url.php.gtk;">PHP-GTK</link>, que generalmente
suelen ejecutarse durante más tiempo que scripts para la Web; el nuevo
mecanismo marcará la diferencia en cuanto a las fugas de memoria
progresivas en el tiempo.
</para>
</sect2>
</sect1>
</chapter>
<!-- Keep this comment at the end of the file
vim600: syn=xml fen fdm=syntax fdl=2 si
vim: et tw=78 syn=sgml
vi: ts=1 sw=1
-->