mod_perl と Perl インタプリタプール

前々回も書きましたが,worker MPM で mod_perl を使うと,インタプリタプールなるものが作成されます。このインタプリタプールというのはまさに Apache におけるプロセスプール(prefork MPM の場合)やスレッドプール(worker MPM の場合)の Perl インタプリタ版のようなものです。

このインタプリタプールでの PerlInterpMax(総数の最大)はデフォルトで 5 になっています。ですから,「prefork MPM から worker MPM にしたらメモリ消費量がガツンと減ったぜ!httpd の thread 数 150 でぶんまわしてるのに!スレッドマンセー!」とぬか喜びしていると,実は Perl は並列に 5 つしか走っていなかった,ということがありえます。

2007/09/27 追記。adiary 作者さんからトラバいただきました(⇒PerlInterpMax の示すもの - adiary開発日誌)。PerlInterpMax というのは Apache 全体のインタプリタ数を示すのではなく,各プロセス毎のインタプリタ数を示すようです。worker モデルでもデフォルトの ServerLimit は 16 個なのでプロセスが最大 16 個になってしまい,結構大量のインタプリターを生成するようです。つーか詳しくは当該トラバ参照。トラックバックありがとうございました。わたしも激しく勘違いしていました。

mod_perl におけるスレッドモデルについてはいくつかの場所に記述が分散しているのですが,今日は http://perl.apache.org/docs/2.0/user/design/design.html#Interpreter_Management あたりを訳してみました。といってもいつもの通り超意訳&抄訳,PerlInterpScope のところは未訳(Apache の subrequest というものがなんぞや,というイメージがまだつかめていないため)というていたらくです。

インタプリタの管理(抄訳,中途)

マルチスレッド環境で mod_perl をサポートするために,mod_perl-2.0 は Perl の ithread(Perl 5.6.0 からの新機能)*1を利用しています。この機能は Perl ランタイムをスレッドセーフな PerlInterpreter 構造へとカプセル化するものです。mod_perl でリクエストを処理するスレッドは,各々 PerlInterpreter インスタンスが必要となります。

PerlInterpreter を各 httpd スレッド毎に一対一対応させる代わりに,mod_perl では設定自在なインタプリタプールを管理しています。この方式により,必要最小限な数のインタプリタを用意することでメモリ使用量を押さえることができます。また,各インタプリタで既に使用されたメモリアロケーションを再利用することができます。これは 1.3.x 時代のモデルでは不可能なことでした。

インタプリタプールは Perl を -Dusethreads オプション付きでビルドした場合のみ有効です。さもなくば mod-perl 1.0 のように振る舞います(つまり単一のインタプリタを利用する)。

httpd サーバがスタートするとき,Perl インタプリタが構築され,設定ファイルで指定されたすべてのコードがプリコンパイルされます(ここまでは 1.0 と同様の挙動です)。ここで作成されたインタプリタは「親」インタプリタと呼ばれます。そして親インタプリタの(スレッドセーフな)クローンを PerlInterpStart 設定子で指定された数だけ perl_clone() によって作り,インタプリタプールに追加します。このクローンは書き込み可能なデータ(シンボルテーブル等)を親からコピーし,コンパイルされたシンタックスツリーを共有します。

たとえば以下のようにいくつかの適当なモジュールを読み込む startup.pl スクリプトで計測してみたところ,

use CGI ();
use POSIX ();
use IO ();
use SelfLoader ();
use AutoLoader ();
use B::Deparse ();
use B::Terse ();
use B ();
use B::C ();

インタプリタは 6M のメモリを消費しましたが,子インタプリタは各々その半分以下のサイズ,約 2.3M しか消費しませんでした(シンタックスツリーを共有できているおかげですね)。

注: この結果は Perl 5.6.0 の perl_clone() と GvSHARED 最適化にメモリリークがあることが発見される前に測定したものです。

Perl*Handler が設定されている場合,リクエスト時に,利用可能なインタプリタがプールから割り当てられます。conn_rec 構造体と request_rec 構造体はスレッド毎に存在しており,ポインタはconn_rec->pool や request_rec->pool に保持されます(これらはリクエスト生存中に利用されたりします)。

スレッドが実行されていない間に呼ばれるハンドラ(PerlChildInitHandler と PerlChildExitHandler のことです)では,親インタプリタが使用されます。インタプリタプールを制御するためのいくつかの設定子があります。

PerlInterpStart

起動時にクローンするインタプリタの数です。
(訳注: デフォルト 3)
(訳注: StartServers のインタプリタプール版みたいなもの)

PerlInterpMax

(訳注: デフォルト 5)
(訳注: ServerLimit のインタプリタプール版みたいなもの)
プール内のインタプリタが全部使用されている場合,mod_perl はリクエストを処理するために新しいインタプリタをクローンしますが,この設定子はインタプリタ総数の最大値となります。PerlInterpMax に達した場合,mod_perl は(COND_WAIT() により)ブロックします。インタプリタが余って利用可能になるとブロックから戻ります(COND_SIGNAL() によってシグナルされることで)。

PerlInterpMinSpare

(訳注: デフォルト 3)
(訳注: MinSpareServers のインタプリタプール版みたいなもの)

PerlInterpMaxSpare

(訳注: デフォルト 3)
(訳注: MaxSpareServers のインタプリタプール版みたいなもの)

PerlInterpMaxRequests

(訳注: デフォルト 2000)
(訳注: MaxRequestsPerChild のインタプリタプール版みたいなもの)
一つのインタプリタが処理するリクエストの最大数です。処理済リクエストがこの数に達するとインタプリタは終了させられ,新しいインタプリタ(クローン)で置き換えられます。

PerlInterpScope

(訳注: デフォルト request)

訳注

PerlInterpScope はディレクトリ単位( 等),それ以外は Server 単位の設定(VirtualHost 等)になります。

*1:Perl の ithread については http://www.donzoko.net/doc/memo/perlithreads.html がまとまっています