Considérations sur les performances

Nous avons dĂ©jĂ  vu dans les sections prĂ©cĂ©dentes que la collecte des racines probables avait un impact trĂšs lĂ©ger sur les performances, mais c'est lorsque l'on compare PHP 5.2 Ă  PHP 5.3. MĂȘme si l'enregistrement des racines probables est plus lent que de ne pas les enregistrer du tout, comme dans PHP 5.2, d'autres amĂ©liorations apportĂ©es par PHP 5.3 font que cette opĂ©ration ne se ressent pas au niveau des performances.

Il y a principalement deux niveaux pour lesquels les performances sont affectées. Le premier est l'empreinte mémoire réduite, et le second est le délai à l'exécution, lorsque le mécanisme de nettoyage effectue son opération de libération de mémoire. Nous allons étudier ces deux axes.

Empreinte mémoire réduite

Avant tout, la raison principale de l'implĂ©mentation du mĂ©canisme de collecte des dĂ©chets est la rĂ©duction de la mĂ©moire consommĂ©e, en nettoyant les rĂ©fĂ©rences circulaires lorsque les conditions requises sont remplies. Avec PHP, ceci arrive dĂšs que le tampon de racines est plein, ou lorsque la fonction gc_collect_cycles() est appelĂ©e. Sur le graphe ci-aprĂšs, nous affichons l'utilisation mĂ©moire du script suivant, avec PHP 5.2 et avec PHP 5.3, en excluant la mĂ©moire obligatoire que PHP consomme pour lui-mĂȘme au dĂ©marrage.

Exemple #1 Exemple d'utilisation mémoire

<?php
class Foo
{
public
$var = '3.14159265359';
public
$self;
}

$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";
}
}
?>
Comparaison de la consommation mémoire entre PHP 5.2 et PHP 5.3

Dans cet exemple quelque peu acadĂ©mique, nous crĂ©ons un objet possĂ©dant un attribut le rĂ©fĂ©rençant lui-mĂȘme. Lorsque la variable $a dans le script est rĂ©assignĂ©e Ă  l'itĂ©ration suivante, une fuite mĂ©moire apparaitra. Dans ce cas, les deux conteneurs zval fuient (la zval de l'objet et celle de l'attribut), mais une seule racine possible est trouvĂ©e : la variable qui a Ă©tĂ© supprimĂ©e. Lorsque le tampon de racines est plein aprĂšs 10.000 itĂ©rations (avec un total de 10.000 racines possibles), le mĂ©canisme de collecte des dĂ©chets entre en jeu et libĂšre la mĂ©moire associĂ©e Ă  ces racines probables. Cela se voit trĂšs clairement sur les graphes d'utilisation mĂ©moire de PHP 5.3. AprĂšs chaque 10.000 itĂ©rations, le mĂ©canisme se dĂ©clenche et libĂšre la mĂ©moire associĂ©e aux variables circulairement rĂ©fĂ©rencĂ©es. Le mĂ©canisme en question n'a pas Ă©normĂ©ment de travail dans cet exemple, parce que la structure qui a fui est extrĂȘmement simple. Le diagramme montre que l'utilisation maximale de mĂ©moire de PHP 5.3 est d'environ 9Mo, lĂ  oĂč elle n'arrĂȘte pas d'augmenter avec PHP 5.2.

Ralentissements durant l'exécution

Le second point oĂč le mĂ©canisme de collecte des dĂ©chets (GC) affecte les performances est lorsqu'il est exĂ©cutĂ© pour libĂ©rer la mĂ©moire "gaspillĂ©e". Pour quantifier cet impact, nous modifions lĂ©gĂšrement le script prĂ©cĂ©dent afin d'avoir un nombre d'itĂ©rations plus Ă©levĂ© et de supprimer la collecte de l'usage mĂ©moire intermĂ©diaire. Le second script est reproduit ci-dessous :

Exemple #2 Impact de GC sur les performances

<?php
class Foo
{
public
$var = '3.14159265359';
public
$self;
}

for (
$i = 0; $i <= 1000000; $i++ )
{
$a = new Foo;
$a->self = $a;
}

echo
memory_get_peak_usage(), "\n";
?>

Nous allons lancer ce script 2 fois, une fois avec zend.enable_gc Ă  on, et une fois Ă  off:

Exemple #3 Lancement du script ci-dessus

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

Sur ma machine, la premiĂšre commande semble durer tout le temps 10,7 secondes, alors que la seconde commande prend environ 11,4 secondes. Cela correspond Ă  un ralentissement de 7% approximativement. Cependant, la quantitĂ© totale de mĂ©moire utilisĂ©e par le script est rĂ©duite de 98%, passant de 931Mo Ă  10Mo. Ce benchmark n'est pas trĂšs scientifique ou mĂȘme reprĂ©sentatif d'applications rĂ©elles, mais il dĂ©montre concrĂštement en quoi le mĂ©canisme de collecte des dĂ©chets peut ĂȘtre utile au niveau de la consommation mĂ©moire. Le bon point est que le ralentissement est toujours de 7%, dans le cas particulier de ce script, alors que la mĂ©moire prĂ©servĂ©e sera de plus en plus importante au fur et Ă  mesure que des rĂ©fĂ©rences circulaires apparaitront durant l'exĂ©cution.

Statistiques internes du GC de PHP

Il est possible d'obtenir quelques informations supplémentaires concernant le mécanisme de collecte des déchets interne à PHP. Mais pour cela, il faut recompiler PHP avec le support du benchmarking et de la collecte de données. Il est nécessaire de renseigner la variable d'environnement CFLAGS avec -DGC_BENCH=1 avant de lancer ./configure avec les options souhaitées. L'exemple suivant démontre cela :

Exemple #4 Recompiler PHP pour activer le support du benchmark du GC

export CFLAGS=-DGC_BENCH=1
./config.nice
make clean
make

Lorsque le code du script ci-dessus est ré-exécuté avec le binaire PHP fraichement reconstruit, le résultat suivant devrait apparaßtre aprÚs l'exécution :

Exemple #5 Statistiques GC

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

Les statistiques les plus intéressantes sont affichées dans le premier bloc. On voit ici que le mécanisme de collecte des déchets a été déclenché 110 fois, et qu'au total ce sont plus de 2 millions d'allocations mémoire qui ont été libérées durant ces 110 passages. DÚs lors que le mécanisme est intervenu au moins une fois, le pic du buffer racine est toujours de 10000.

Conclusion

De maniÚre générale, la collecte des déchets de PHP ne causera un ralentissement que lorsque l'algorithme de collecte de cycles tournera, ce qui signifie que dans les scripts normaux (plus courts), il ne devrait pas du tout y avoir d'impact sur les performances.

Cependant, lorsque le mécanisme de collecte de cycles sera déclenché dans des scripts normaux, la réduction de l'empreinte mémoire permettra l'exécution parallÚle d'un nombre plus important de ces scripts, puisque moins de mémoire sera utilisée au total.

Les avantages se sentent plus nettement dans le cas de scripts démons ou devant tourner longtemps. Ainsi, pour les applications » PHP-GTK qui tournent souvent plus longtemps que des scripts pour le Web, le nouveau mécanisme devrait réduire significativement les fuites mémoire sur le long terme.

add a note

User Contributed Notes 2 notes

up
22
Dmitry dot Balabka at gmail dot com ¶
8 years ago
There is a possibility to get GC performance stats without PHP recompilation. Starting from Xdebug version 2.6 you are able to enable stats collection into the file (default dir /tmp with name gcstats.%p):

php -dxdebug.gc_stats_enable=1 your_script.php
up
19
Talisman ¶
10 years ago
The GC, unfortunately, as expounded in the examples above, has the tendency to promote lazy programming.
Clearly the benefits of the GC to assist in memory management are there, and help to maintain a stable system, but it is no excuse to not plan and test your code properly.
Always re-read your code critically and objectively to ensure that you are not introducing memory leaks unintentionally.