パフォーマンスの考慮点

可能性があるルートを単純に収集すると、パフォーマンスにごくわずかな影響があると既に前述しました。 しかし、これは PHP 5.3 と PHP 5.2 を比較する場合です。 可能性があるルートを記録すると、PHP 5.2 のように全く記録しないものに比べてより遅いとはいえ、 PHP 5.3 のランタイムへの他の変更点により、この特有のパフォーマンス低下が一層際立つことが防止されています。

パフォーマンスが影響を受ける主な分野は2つあります。 1つ目は、減少したメモリ使用量で、 2つ目はガベージコレクション機構がそのメモリ・クリーンアップを実行する際の実行遅延です。 それら両方の問題を見てみましょう。

減少したメモリ使用量

まず第一に、ガベージコレクション機構を実装する理由は、 必要条件が満たされたらすぐに、循環参照された変数を整理してメモリ使用量を減らすことにあるのです。 PHP の実装では、ルート・バッファが満杯になるか、または、関数 gc_collect_cycles が呼ばれるとすぐにこれが起こります。 下図で、下記のスクリプトのメモリ使用量を PHP 5.2 及び PHP 5.3 の両方で示します。 (ただし)起動時に PHP 自身が使用する基底メモリを除きます。

例1 メモリ使用量の例

<?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";
    }
}
?>
PHP 5.2 と PHP 5.3 のメモリ使用量の比較

このとても学術的な例では、 プロパティがオブジェクト自身を指すように設定されたオブジェクトを作成します。 スクリプト内の $a 変数がループの次の繰り返しで再設定されると、 一般的にメモリ・リークが発生します。 この場合、zval コンテナが2つリークします(オブジェクトの zval 及び プロパティの zval)。 しかし、可能性があるルートが一つだけ見つかります。 (それは)アンセットされた変数です。 一万回繰り返した後に(可能性があるルートを合計1万件伴って)ルート・バッファが満杯になると、 ガベージコレクション機構が有効になって、それらの可能性があるルートと関連するメモリを解放します。 これは PHP 5.3 での、のこぎりの歯のようなメモリ使用量グラフでとてもはっきりと分かります。 一万回繰り返す毎に機構が有効になって、循環参照された変数と関連するメモリを解放します。 この例ではリークした構造がとても単純なので、機構そのものが多くの仕事をする必要はありません。 図では、PHP 5.3 でのメモリ使用量の最大はおよそ 9MB と分かります。 ところが PHP 5.2 では、メモリ使用量は増加し続けます。

ランタイムの減速

ガベージコレクション機構がパフォーマンスに影響する2つ目の分野は、 ガベージコレクション機構が「リークした」メモリを解放するために 開始する際にかかる時間です。これがどの程度か見るためには、 反復回数をより多くし、間に入るメモリの使用量を除去するために、 前のスクリプトをちょっと修正します。 2つ目のスクリプトはこれです。

例2 GC パフォーマンスの影響

<?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";
?>

このスクリプトを2回実行します。最初は zend.enable_gc 設定をオンにし、次はオフにします。

例3 上記のスクリプトを実行

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

私のマシンでは、最初の命令は一貫しておよそ10.7秒かかるようです。 ところが、第2の命令にはおよそ11.4秒かかります。 これは、およそ7%の減速です。 しかしながら、スクリプトで使用されるメモリの最大量は、931MB から 10MB まで 98% 減ります。 このベンチマークはあまり学術的でもなく、現実のアプリケーションの代表でもありません。 しかし、このガベージコレクション機構が提供するメモリ使用量の利点を示します。 良い点は、スクリプト実行中に循環参照をより多く見つけて、 メモリ節約機能がますます多くのメモリを節約する一方で、 この個別のスクリプトで減速がいつでも同じく7%ということです。

PHP 内部の GC 統計

ガベージコレクション機構がPHP 内部から実行される方法に関して、 情報をもう少し上手に扱うことができます。 しかし、そうするためには、ベンチマークとデータ収集コードを使用可能にするために、 PHP を再コンパイルしなければなりません。 希望するオプションで ./configure を走らせる前に、 CFLAGS 環境変数を -DGC_BENCH=1 に設定しなければなりません。 以下の順序で目的に達するでしょう。

例4 GC ベンチマーキングを使用可能にするために PHP を再コンパイル

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

新しくビルドした PHP バイナリで上記のコード例を前と同じように実行すると、 PHP の実行終了後に、下記が表示されているでしょう。

例5 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

最も有益な統計は、最初のブロックで示されます。 ここではガベージコレクション機構が 110 回実行されたことが分かります。 そして、全体では、それら 110 回の実行中に、 200万以上のメモリ割り当てが 解放されました。 ガベージコレクション機構が少なくとも一回実行されるや否や、 "Root buffer peak" は常に 10000 です。

結論

通常、循環収集アルゴリズムが実際に動作する際、PHP でのガベージコレクタは減速を 引き起こすだけです。一方、通常の(より小さい)スクリプトでは、打撃を受ける パフォーマンスが全くあってはいけません。

しかしながら、循環収集機構が通常のスクリプトに対して動作する場合には、 全体でそれほど多くのメモリが使われないので、 循環収集機構により提供されるメモリ節減により、それらのスクリプトの多くで サーバー上で平行して稼動できるようになります。

より長時間にわたるスクリプト(例えば長いテスト・スイートまたはデーモン・スクリプト)にとって 有利なことはとても明らかです。 また、ウェブ用のスクリプトに比べてたいていより長く動作する傾向がある » PHP-GTK アプリケーションについては、 時がたつにつれて忍び込むメモリリークに関して、新しい機構によるかなりの差が生じなければなりません。