おぎろぐはてブロ

なんだかんだエンジニアになって10年以上

clearstatcache()のはなし

前々回前回のrealpath cacheの話は実のところこの話の前振り。(なんだけど、前振りの方が長くて役立つ。
clearstatcache()を叩くと、realpath cacheもクリアされるよというのが前回までの話なのですが、clearstatcache()の関数リファレンスにはこのように説明が書かれています。

void clearstatcache( void )

stat やlstat、またはその他の関数(後述)を使用すると、PHPはパフォーマンス向上のために それらの関数の戻り値をキャッシュします。しかし、ケースによっては、 キャッシュされた情報を消去したい場合もあるでしょう。例えば、一つのスクリプト上で同じファイルが何度もチェックされ、そのファイルが変更されたり削除されたりする可能性がある場合、ステータスキャッシュを消去しなければならないと感じるでしょう。 このようなケースでは、clearstatcache()を使用することでファイルの情報に関してPHPが持っているキャッシュをクリアすることができます。


PHPは存在しないファイルについての情報はキャッシュしないことにも注意してください。もし存在しないファイルに対して file_exists() をコールする場合、ファイルを作成するまで この関数は FALSE を返します。もしファイルを作成した場合、たとえファイルを削除したとしても TRUE を返します。しかし、unlink() はキャッシュを自動的にクリアします。


注意: この関数は特定のファイルに関する情報をキャッシュします。したがって、 同じファイルについて複数回の操作を行いそのファイルに関する情報を キャッシュされないようにするためには、 clearstatcache()をコールするだけです。


影響を受ける関数を以下に示します。 stat(), lstat(), file_exists(), is_writable(), is_readable(), is_executable(), is_file(), is_dir(), is_link(), filectime(), fileatime(), filemtime(), fileinode(), filegroup(), fileowner(), filesize(), filetype(), および fileperms().

PHP: clearstatcache - Manual

ファイルは最後の1ファイルのみがキャッシュされる

説明を読むと、なるほどこれらの関数を叩くと結果がキャッシュされるのね、というのは分かるのですが、この中で注意すべき点は、

この関数は特定のファイルに関する情報をキャッシュします。 (This function caches information about specific filenames)

というところ。実は、キャッシュされるのは最終の1ファイルのみです。 (この原文から読み取れないけどね)

なので、このキャッシュは、どちらかというと、

if (is_dir($filename) && filemtime($filename) < $hoge) {
  ...
}

的な利用を考慮しているものということ。

例を挙げると、

for ($i = 0; $i < 10000; ++$i) {
  filetype($file1);
  filetype($file2);
}

と、

for ($i = 0; $i < 10000; ++$i) {
  filetype($file1);
}
for ($i = 0; $i < 10000; ++$i) {
  filetype($file2);
}

とでは、後者のがキャッシュが有効で圧倒的に速い。(意味ないけどね)

リファレンス通りの動作をしているのは、Windows以外では、PHP4.3未満

困りはしないですが、リファレンスに説明されている通りに動作するのは、PHP4.3未満です。

PHP4.3.0以降では、Windows環境以外では、is_readable() / is_writable() / is_executable() / file_exists() 関数については、access(2)をコール (現在は、VCWD_ACCESS()をコール)するようになっており、これは上記キャッシュの対象外になります。

また、PHP5以降では、php_stream_stat_path()を内部的に呼び出すようになり、ローカルのファイル以外でもstat()系の値を返却できるようになりましたが、ローカルファイル以外はキャッシュ対応外です。(対応できないこともないと思うけど)

PHP5.1以降からはrealpathのcacheをクリアするようになった

これは前に書いたとおり。
clearstatcache()の実装はシンプルで、php_clear_stat_cache()を呼び出しているだけ。

 717 /* {{{ proto void clearstatcache(void)
 718    Clear file stat cache */
 719 PHP_FUNCTION(clearstatcache)
 720 {
 721     if (ZEND_NUM_ARGS()) {
 722         WRONG_PARAM_COUNT;
 723     }
 724     php_clear_stat_cache(TSRMLS_C);
 725 }
 726 /* }}} */

で、BG(CurrentStatFile)BG(CurrentLStatFile)をクリアして、realpath_cache_clean()で全クリア

 701 /* {{{ php_clear_stat_cache()
 702 */
 703 PHPAPI void php_clear_stat_cache(TSRMLS_D)
 704 {
 705     if (BG(CurrentStatFile)) {
 706         efree(BG(CurrentStatFile));
 707         BG(CurrentStatFile) = NULL;
 708     }
 709     if (BG(CurrentLStatFile)) {
 710         efree(BG(CurrentLStatFile));
 711         BG(CurrentLStatFile) = NULL;
 712     }
 713     realpath_cache_clean(TSRMLS_C);
 714 }

BG(CurrentStatFile)BG(CurrentLStatFile)には、ファイル名が格納されていて、stat/lstatの結果を格納してあるのは、BG(sb)で、ファイル名が一致してればキャッシュを返し、そうでなければ普通に呼び出してキャッシュを更新するという流れです。
しかし、realpath_cache_clean()をここに入れるのはどうなのよというのもあるし、マニュアルに書いてないのもどうなのよと思った。何か理由があったり、自分の勘違いだったりするのかなぁ。。

[2009/06/23追記] PHP5.3からはデフォルトではrealpathのcacheをクリアしないようになった

5.1から挙動が変わったclearstatcache()ですが、5.3ではデフォルト動作が従来通りに戻ります。要は、realpath cacheのクリアをしなくなりました。

5.3では、オプション引数が2つ追加されます。

void clearstatcache ([ bool $clear_realpath_cache= false [, string $filename ] ] )

PHP: clearstatcache - Manual
clear_realpath_cache
realpath cacheをクリアするか (デフォルトはfalse)
filename
clear_realpath_cacheがtrueのときに、ファイル名を指定して、そのファイルのみのキャッシュをクリア

なんというか、、関数分ければいいのに。。