理想未来ってなんやねん

娘可愛い。お父さん頑張る。

PHPのガーベジコレクタについて

PHP 5.2までのガーベジコレクタは参照カウント方式です。
循環参照が発生すると、その参照に含まれるオブジェクトが回収できませんので、デーモンなど長時間実行するようなスクリプトを作る場合は循環参照を起こさないように注意する必要があります。

尚、PHP 5.3からは循環参照コレクタが使え、デフォルトで有効になっています。
実際にPHP 5.1.6と5.3.0を比較し、実際にどのような動きになるか以下のコードで試してみました。

<?php

ini_set('memory_limit', -1);
// gc_disable();

class A
{
}

$loop_count = 1000000;
for($i = 1; $i <= $loop_count; $i++)
{
    $a = new A;
    $a->self = $a; // 循環参照
    if (($i % round($loop_count / 10)) ==  0)
    {
        printf("%10d: %10d KB\n", $i, memory_get_usage() / 1024);
    }
    // unset($a->self);
}

if ( function_exists('memory_get_peak_usage') )
{
    // (PHP 5 >= 5.2.0)
    printf("max %10d KB\n", memory_get_peak_usage() / 1024);
}

PHP 5.1.6の場合

PHP 5.1.6だと、以下のような結果になります。
循環参照されている状態だと、どんどん増え続けます。

$ php mem_test.php
    100000:      31947 KB
    200000:      63872 KB
    300000:     101942 KB
    400000:     127723 KB
    500000:     153504 KB
    600000:     203861 KB
    700000:     229643 KB
    800000:     255424 KB
    900000:     281205 KB
   1000000:     306986 KB

unsetの所のコメントアウトを外して開放するようにすると以下のような結果になります。
循環参照されていないので、メモリの開放は正しく行われます。

    100000:        622 KB
    200000:        622 KB
    300000:        622 KB
    400000:        622 KB
    500000:        622 KB
    600000:        622 KB
    700000:        622 KB
    800000:        622 KB
    900000:        622 KB
   1000000:        622 KB

PHP 5.3.0の場合

PHP 5.3.0の結果。一定以上から変化しません。
循環参照コレクタがうまく働いているようです。

$ php mem_test.php
    100000:       1582 KB
    200000:       1582 KB
    300000:       1582 KB
    400000:       1582 KB
    500000:       1582 KB
    600000:       1582 KB
    700000:       1582 KB
    800000:       1582 KB
    900000:       1582 KB
   1000000:       1582 KB
max       5139 KB


循環参照コレクタの有効/無効の変更はgc_enable/gc_disable関数で行います。
gc_disableの所のコメントアウトを外すと、PHP 5.3.0でもメモリが増え続けます。

$ php mem_test.php
    100000:      43125 KB
    200000:      85692 KB
    300000:     136451 KB
    400000:     170827 KB
    500000:     205202 KB
    600000:     272345 KB
    700000:     306720 KB
    800000:     341096 KB
    900000:     375471 KB
   1000000:     409846 KB
max     409863 KB

尚、gc_collect_cycles関数で強制的にガベージを収集することができますが、gc_disableで循環参照コレクタを無効にしている時に確保したメモリは収集されないようですので、わざわざ無効にする必要はないと思います。
また、gc_collect_cycles関数を使うと、わずかながらメモリが増えていく現象が見られましたので、ガベージの収集も基本的にはPHPに任せるようにした方が良いようです。


当然正しく開放されるようにプログラムを組むべきですが、長時間実行されるようなスクリプトを作る場合、石橋を叩いて渡るなら5.3以降を使うようにした方が良さそうです。

個人的な意見としては、開発時はgc_disableで開発し、テスト・運用時にgc_enableで動かすのが良いのではないかと思います。