PHP Extensionを作ろう第4回 - Extension開発に適したPHPを用意する
はじめまして、@hnwと申します。一部の方々に非常に人気があったシリーズ「PHP Extensionを作ろう」久々の続編です。といっても、今回はExtensionのソースコードは一行も出てきません。Extensionを作る準備段階の話題です。
PHP Extension開発時にオススメのPHPビルドオプションがあるのをご存じでしょうか。これは「拡張モジュール開発用に PHP をビルドする方法」でも紹介されているのですが、「--enable-debug --enable-maintainer-zts」というものです。
本稿ではこのビルドオプションについて解説し、php-buildを利用して環境構築する方法についても紹介します。
PHPのメモリ管理の概要
まずPHPのメモリ管理について簡単に紹介します。
Apache prefork MPM+mod_phpの組み合わせを例に挙げますと、Apacheはリクエストを処理するための子プロセスを複数起動します。この子プロセスはMaxRequestsPerChildで指定された回数(数百から数千程度にすることが多いはずです)のリクエストを処理するまで終了しません。つまり、mod_phpがリクエストのたびにメモリリークするようだと、その影響が数百倍から数千倍になる可能性があるわけです。
こうしたメモリリークへの対策として、PHPではemalloc()、efree()といったCの関数を提供しています。これは、1リクエストが終了すると勝手にメモリを解放するようなmalloc系関数のラッパー関数です。これらの関数でメモリ確保をしている限り、万一メモリの解放忘れがあっても1リクエスト内にしか影響せず、被害を最低限にできるというわけです。
とはいえ、emalloc関数を使っているからといってメモリ解放を忘れていいわけではありません。PHPでバッチプログラムを長時間実行するような状況など、メモリリークの影響がどんどん蓄積していく状況は考えられます。そうでなくても大量のメモリを無駄使いするようだと短時間の処理でも悪影響が出てきますので、動的に確保したメモリは不要になった時点で解放すべきです。
一方で、プログラマがメモリ解放を忘れる可能性をゼロにはできませんし、マクロ内でメモリ確保されている場合などプログラマが自分でメモリ確保したことに気づかない状況も考えられます。筆者個人の感想ですが、PHPのメモリ管理は比較的難解であり、人間の脳だけでメモリリークを防ぐのは困難だと感じます。
メモリリークを検出する
実は、PHPには標準でメモリリークの検出機構があります。これはExtensionを書く場合にたいへん有用です。
このメモリリーク検出機構はPHPを--enable-debugオプションつきでビルドすることで有効になります。また、この機能を使うのに特別なライブラリは不要です。
メモリリーク検出の仕組みとしては、emalloc()で確保したメモリのうちリクエスト終了までにefree()されなかったものについて情報を表示するというものです。PHPがメモリリークを検出すると次のようなデバッグメッセージを出力します。
foo.c(123) : Freeing 0x10749B630 (22 bytes), script=foo.php
上の例であれば、foo.cの123行目で確保したメモリ22バイトが解放されていないよ、と教えてくれるわけです。単純ですが強力な仕組みではないでしょうか。
ちなみに、--enable-debugオプションつきのPHPは遅いので、常用するPHPとは別にビルドした方がよいと思います。
ZTS対応について
上で紹介したもう一つのオプションについても解説します。
PHPを--enable-maintainer-ztsオプションつきでビルドすると、ZTS(Zend Thread Safety)対応になります。これは本来はマルチスレッド環境(Apache Worker MPMやIIS)でPHPを動かすための機構ですが、このオプションを付けるとCLI版でもZTS用の処理が有効になります。
このZTS版のPHP CLIを利用することで、PHP ExtensionをZTS版としてビルドしたりテストしたりすることができます。
今後ZTS環境は消えゆく運命だろうと思うので、自分のExtensionをZTSに対応させる意味はそれほど無いように思います。とはいえ、ZTS対応のためのマクロを正しく使えているかなどプログラムの正当性の確認にもなりますから、ZTS環境でビルドが通るようにしておいて損はないでしょう。
php-buildでメモリリーク検出とZTSが有効なPHPを作る
上記の2オプションを有効にしたPHPをphp-buildを使ってインストールする方法を紹介します。
php-buildというのは、複数のPHP環境を構築するためのツールです。phpenvと併用すれば複数のPHP環境を簡単に切り替えられるので、非常に便利です。
php-buildで今回のExtension開発用のPHPをビルドするには、独自の定義ファイルを作る必要があります。筆者の環境ではdefinitionディレクトリが $HOME/.phpenv/plugins/php-build/share/php-build/definitions にありますので、ここに5.4.7-debugのようなファイルを作ります。
$ diff 5.4.7 5.4.7-debug
0a1,4
> configure_option -D "--disable-debug"
> configure_option "--enable-debug"
> configure_option "--enable-maintainer-zts"
>
$
このように、元の定義ファイルをコピーしてconfigureオプション「--enable-debug」「--enable-maintainer-zts」を追加するだけです。
この定義ファイルで作成したPHPを使ってExtensionの「make test」を走らせれば、普段のテストケースについてメモリリークが無いかどうかを確認できるというわけです。
筆者はこのようにして作ったPHPを使ってExtensionの動作確認をしています。コマンド例を示します。
$ phpenv global 5.2.17-debug
$ make clean && phpize && ./configure && make && make test NO_INTERACTION=1
(略)
=====================================================================
TEST RESULT SUMMARY
---------------------------------------------------------------------
Exts skipped : 0
Exts tested : 44
---------------------------------------------------------------------
Number of tests : 21 20
Tests skipped : 1 ( 4.8%) --------
Tests warned : 0 ( 0.0%) ( 0.0%)
Tests failed : 0 ( 0.0%) ( 0.0%)
Expected fail : 0 ( 0.0%) ( 0.0%)
Tests passed : 20 ( 95.2%) (100.0%)
---------------------------------------------------------------------
Time taken : 5 seconds
=====================================================================
$ phpenv global 5.3.17-debug
$ make clean && phpize && ./configure && make && make test NO_INTERACTION=1
(略)
$ phpenv global 5.4.7-debug
$ make clean && phpize && ./configure && make && make test NO_INTERACTION=1
(略)
$
この例では3バージョンのPHPで順にmake testしています。このように、phpenvを使うと気軽にメモリリークのテストができますので、Extensionを書く方はぜひ一度試してみてください。