PHP5.1から実装されたrealpath cacheについて(1) - 概要と、デフォルトのキャッシュサイズが小さめというはなし
PHP5.1から、パフォーマンス対策として、realpath cacheというものが導入されています。
PHPがファイルを開く際(実行ファイル本体から、include/require、fopenなど基本的には全てのファイルアクセスが対象)に、open_basedirの検証や、include_pathの探索のために、realpath(3)を呼び出し、その中でstat(2)を大量に呼び出しており、ファイルをたくさん扱う場合には、結構ボトルネックとなっていました。(realpathとは正規化された絶対パス名を返すもので、パス中のシンボリックリンクをすべて展開する関数です)
ここらへんの実際の動きについては前に書いてます。
Yahoo!では、このキャッシュがなかったころ(PHP4の時代)、realpathを呼び出さないようにパッチをあてており、Derickがそんなパッチを作ったのをあててみたら、30%スピードアップという話。
とはいえ、セキュリティ上制約を与える必要があったり、realpathを見ないことによる弊害もあるわけで、この結果をキャッシュするようにしたのがrealpath cacheです。
キャッシュ設定
php.iniで、キャッシュサイズと、生存時間(TTL)を設定することができます。デフォルト値は、16K (byte)と120秒です。
PHP: Description of core php.ini directives - Manualパフォーマンスチューニング
名前 デフォルト 変更の可否 変更履歴 realpath_cache_size "16K" PHP_INI_SYSTEM PHP 5.1.0 以降で使用可能。 realpath_cache_ttl "120" PHP_INI_SYSTEM PHP 5.1.0 以降で使用可能。 以下に設定ディレクティブに関する簡単な説明を示します。
realpath_cache_sizeの値は、小さいかも
デフォルト値は、16K = 16,000byteとなっていますが、これで適切なのかどうか。コードを眺めてみます。
キャッシュ周りの処理は、TSRM/tsrm_virtual_cwd.c にあります。
TSRM/tsrm_virtual_cwd.c (PHP5.2.6) line:436
static inline void realpath_cache_add(const char *path, int path_len, const char *realpath, int realpath_len, time_t t TSRMLS_DC) /* {{{ */ { long size = sizeof(realpath_cache_bucket) + path_len + 1 + realpath_len + 1; if (CWDG(realpath_cache_size) + size <= CWDG(realpath_cache_size_limit)) { realpath_cache_bucket *bucket = malloc(size); unsigned long n; bucket->key = realpath_cache_key(path, path_len); bucket->path = (char*)bucket + sizeof(realpath_cache_bucket); memcpy(bucket->path, path, path_len+1); bucket->path_len = path_len; bucket->realpath = bucket->path + (path_len + 1); memcpy(bucket->realpath, realpath, realpath_len+1); bucket->realpath_len = realpath_len; bucket->expires = t + CWDG(realpath_cache_ttl); n = bucket->key % (sizeof(CWDG(realpath_cache)) / sizeof(CWDG(realpath_cache)[0])); bucket->next = CWDG(realpath_cache)[n]; CWDG(realpath_cache)[n] = bucket; CWDG(realpath_cache_size) += size; } }
要は、
long size = sizeof(realpath_cache_bucket) + path_len + 1 + realpath_len + 1;
が1ファイルのエントリのサイズで、realpath_cache_bucketが
typedef struct _realpath_cache_bucket { unsigned long key; char *path; int path_len; char *realpath; int realpath_len; time_t expires; struct _realpath_cache_bucket *next; } realpath_cache_bucket;
ということで、ファイルをたくさん読むようなプログラムだと、往々にしてパスも深く、ファイル名も長ったらしくなるので、構造体と終端文字で30byteと仮においたとして、だいたい1ファイルあたり100byteくらいでなかろうかと。となると、16K (160ファイルくらい)では足りない気がします。64Kくらい?
また、サイズがオーバーすると、新しいパスはキャッシュされないこともコードから読み取れるので、なるべく余裕を持ったサイズ設定を心がけるべきです。
このキャッシュは、APCなどと違って、各プロセス (ZTSならスレッド)がそれぞれ別に持って、別にキャッシュし、プロセス(or スレッド)が終了まで保持されます。DB系にある、pconnectと同じしくみですね。プロセス(or スレッド)の数の分だけメモリを食うことになりますが、まぁ、100 x 64KB でも640KBなのでそんなに気にしなくても。
APCなどOpcode Cacheを利用している場合は、include/requireに関してはまた違う動きをするので、もしかしたらこっちは呼ばれないかもですが、調べていません。
まとめ
- PHP5.1からrealpath cacheという仕組みが導入された
- デフォルト値は16Kと小さめなので、ファイルを多く扱う場合は、キャッシュサイズを大きく設定するほうがよい
- 64Kくらい?
つづく
深追いしすぎていろいろカオスになってきたので、とりあえずここまででアップ。実はこのネタ自体が別のネタの前ふりだったりするんのですが。。
ということで、続きます。
次はこちら