14
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

13行のシンプルなPSR-0準拠APCクラスローダー

Posted at

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]");
}
14
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?