SymfonyのApcUniversalClassLoaderのシンプル版です。
標準的なクラスローダーは、ファイルの存在チェックのために file_exists()
を使っています。なので、そこそこの規模のアプリケーションは、アクセス毎に数百のファイル情報をチェックしているということになります。カーネルがメモリ上にバッファすることもあるので一概には言えませんが、標準的なクラスローダーはI/Oオーバヘッドが潜在的にあるのです。
ApcUniversalClassLoader
は「じゃあクラス=>パスのマップをメモリ上にもってそれを手がかりに一発でファイル引き抜けばI/O減るんじゃない?」という発想から来ています。一度読み込まれたクラスは、APCのキャッシュとして保存されます。二回目以降は、キャッシュ上のマップを手がかりにファイルを探します。デプロイなどで、キャッシュ上のファイルパスと実際のファイルパスが乖離する場合がありますが、それは上記のSymfonyのサイトにも注意書きがあります。
今回作ったのは、ApcUniversalClassLoader
の13行版です。また、以前に書いたワンラインPSR-0準拠クラスオートローダーの発展系でもあります。実装は次のようになります。
<?php
$prefix = 'foo.example.com.';
spl_autoload_register(function($class) use ($prefix) {
if ( false === $path = apc_fetch($prefix.$class) ) {
$nsEnd = strrpos($class, '\\');
$file = strtr(substr($class, 0, $nsEnd), '\\', '/');
$file .= strtr(substr($class, $nsEnd), '\\_', '//');
if ( false !== $path = stream_resolve_include_path($file.'.php') ) {
apc_store($prefix.$class, $path);
}
}
$path and include $path;
});
パスの追加は set_include_path()
を通してやります。
<?php
set_include_path(get_include_path() . PATH_SEPARATOR . __DIR__.'/vendor/twig/twig/lib');
解説
<?php
$prefix = 'foo.example.com.';
// APCはグローバル空間です。
// プロセスごとやアプリごとのスコープはありません。
// なので、キー名にprefixをつけるのが安全です。
// 他のアプリケーションや、プロダクション環境とステージング環境が共存するサーバで、
// キーがバッティングすることを防ぎます。
// とにかく短くしたかったので Closure をそのまま `spl_autoload_register` に渡しています。
spl_autoload_register(function($class) use ($prefix) {
// APC上にキーがクラス名のデータがあるかチェックします。
if ( false === $path = apc_fetch($prefix.$class) ) {
// なければファイルを探しに行きます
// PSR-0の規定で名前空間のアンダースコアはそのままにするという
// 決まりがあるので、名前空間とそれ以下は別々に処理しています。
$nsEnd = strrpos($class, '\\');
$file = strtr(substr($class, 0, $nsEnd), '\\', '/');
$file .= strtr(substr($class, $nsEnd), '\\_', '//');
// ファイルがあるかチェックします。
if ( false !== $path = stream_resolve_include_path($file.'.php') ) {
// あれば、APCに登録します
apc_store($prefix.$class, $path);
}
}
// $path が false でなければ include します。
$path and include $path;
});
最後の行ですが、include_once
ではなく include
にしているのはAPCの性能を最大限に活かすためです。php.iniで apc.stat = 0
にしておく(デフォルトは1)と、APCはキャッシュされたファイルに更新があるか見に行かなくなります。つまり、APC上でクラス名:ファイルパスのマップがあって、そのファイル自体がAPCにキャッシュされているとハードディスクへの問い合わせは0になるということです。したがって、最大限にオーバヘッドを抑えることができます。言い換えると、このクラスローダーは file_exists()
のオーバヘッドと、include
のオーバヘッドを削減できるということです。
ちなみに、APC上のユーザキャッシュを確認するのは APCIterator
が便利です。apc.php
があるならそちらでもいいですね。
<?php
foreach (new APCIterator('user') as $counter) {
var_dump("$counter[key]: $counter[value]");
}