Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
[php]PHP5.3系のGCとメモリ確保を抑える方法

PHP5.3以降のPHP5系では、参照カウント法によるGCが実装されています。

基本

PHPの変数は「zval」というコンテナ(構造体)に格納されます。 PHPは全てzvalで統一されているため、無駄が多いということで、次世代PHPであるPHP7ではzval自体の見直しが図られています。

変数はどのタイミングで解放されるか

基本的には変数がスコープから外れた時点で解放されます。また、スコープ内でもunset()することにより該当の変数は解放されます。

メモリ解放

一般的にLinux(というか大体のよく使われるOS)のユーザープロセスは、終了した時点で確保したメモリを全てOSに返却します。 したがって、ApacheのMaxRequestsPerChildや、php-fpmのpm.max_requestsを設定している場合、そのリクエスト回数に到達した時点でプロセスが終了し、メモリは全て開放されるため、メモリリークによる問題はほとんどありません。 特に近年のサーバはメモリが十分に搭載されていることが多いので、もしメモリリークするようなアプリケーションでも、適切な回数でプロセスが終了されるようにしておけば問題ないでしょう。もちろん再起動は処理速度に影響を与える処理ですので、再起動しないような作りにする=メモリリークしない、ほうがいいです。 また、CLIで動作するコンソールアプリケーションの場合、常駐するようなものでなければ即時に終了するため、余程メモリを使いきらない限りは問題になりません。 最近composerでgc_disable()を入れることによって高速化することが話題になりましたが、composerは「それほど実行時間がかからないコマンドラインアプリケーション」であるため、GCを無視して実行しても問題にならないだろう、というロジックだと思われます。

そもそもメモリ確保を抑える方法

まず基本になることは「メモリを確保しない」ということです。当たり前ですね。 ありがちなのが

function getHoge() {
    return new Hoge();
}

$a = getHoge();
$b = getHoge();

のようなコードです。 Hogeが毎回生成する必要があるものであればこれで構いませんが、実は同じものを使いまわしたいというようなことはよくある話です。 この場合、

function getHoge() {
    static $_hoge = null;
    if ($hoge === null) {
        $hoge = new Hoge();
    }
    return $hoge;
}

$a = getHoge();
$b = getHoge();

のようなコードで回避できます。実際にはstaticよりはクラスのメンバ変数などに保存するのが良いでしょう。このような処理を「メモ化」(memoize)と呼びます。 これを実現するライブラリはいくつかありますが、PHPではどうしても冗長な構文になってしまうので、場合に応じて自前で書くのが良いです。一応php-memoize等があります。(extensionなので導入が少々面倒です)

他にもSQLの問い合わせ結果が大きすぎて大きな配列を確保したり、大きなファイルを読み込んでメモリを消費するなど、色々な要因がありますが、大体のことは「メモ化」で解決できます。 ただし、この場合は変数がスコープから外れてもメモリが解放されなくなるため、スコープ外に入った時点でメモリを解放するような適切な処理が必要になります。 また、Webアプリケーションの場合、1リクエストでメモリが解放されてしまうため、複数リクエストに跨いでメモ化を行いたい場合はAPCuやmemcached等を使うのが良いでしょう。

どうせ開放されるならメモリ確保を抑える必要ないのでは?

どの言語でも同じですが、メモリ確保というのは基本的には「ものすごく遅い処理」です。 特にPHPは他言語に比べnewのコストが大きい印象があります。(ちゃんと調べてないので個人的な感覚です) なので、メモリ消費の少ないアプリケーション=高速なアプリケーションになります。 もちろん単純な変数代入でもメモリは膨らみますし、特に要素数の多い配列はメモリ消費が大きくなります。

Webアプリケーションでメモリ消費の大きい処理を行う

例えば大きいCSVファイルをアップロードしてどうにかするような処理の場合、そのプロセスが大量にメモリを使い、そのメモリがWebサーバのプロセスに残ったままになり、システムを不安定にしてしまう可能性があります。 このような状況を回避したい場合は、CSVの処理を別プロセス、つまり外部プログラムに逃す方法があります。 PHPではproc_openを使うことにより標準入出力によるやりとりを行うことができるので、これを利用するのが良いです。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment