PHP は web サイトを動かすためによく使われています。それもあってか快適なユーザー体験のために様々な実行速度の高速化手法がとられています。
キャッシュは処理高速化を目的とした手法の一つで同じ計算を何度もするのではなく、一度だけ計算してその計算結果をどこかに保存、後からは計算の代わりにその結果の呼出しを行う、という手法です。ある計算が n 回動作するにあたってキャッシュ抜きの計算時間が一度の計算処理にかかる時間 * n
のところ、キャッシュを用いれば一度の計算処理にかかる時間 + 計算結果を保存する時間 + (キャッシュから計算結果を読み取る時間 * n)
となります。計算処理にかかる時間よりキャッシュの読み取り時間が短ければ高速化が成り立ちます。 実装依存ですがキャッシュからの読み取りにはおおよそハッシュ探索が用いられるため探索時間は問題にならず、キャッシュ読み取りのボトルネックは保存済みの結果をメモリやファイルなどから読み取る部分になりがちです。この読み取りにかかる時間ですが、保存場所の形式に大きく影響を受けます。メモリ上ならば速く、ファイル上ならばやや速く、どこぞの通信先のマシン上ならば遅く、みたいな感じです。
PHP の組み込み関数の一部はキャッシュの機能を素の PHP の時点で備えています。opcache 等のキャッシュ用の設定がなくてもキャッシュをします。
PHP: OPcache – Manual
キャッシュする関数は以下です。おおまかにいえばファイルについての情報を得る関数です。
PHP: stat() – Manual
PHP: lstat() – Manual
PHP: file_exists() – Manual
PHP: is_writable() – Manual
PHP: is_readable() – Manual
PHP: is_executable() – Manual
PHP: is_file() – Manual
PHP: is_dir() – Manual
PHP: is_link() – Manual
PHP: filectime() – Manual
PHP: fileatime() – Manual
PHP: filemtime() – Manual
PHP: fileinode() – Manual
PHP: filegroup() – Manual
PHP: fileowner() – Manual
PHP: filesize() – Manual
PHP: filetype() – Manual
PHP: fileperms() – Manual
キャッシュをクリアする関数は次で、この関数の説明の中がこのキャッシュのふるまいに詳しいです。
PHP: clearstatcache – Manual
これらの関数は例えば次の様に動作します。
<?php /** * tmp.txt、tmp2.txt が存在しない状態で実行 * PHPのバージョンは 8.1.0 */ $fileName = __DIR__ . "/tmp.txt"; $fileName2nd = __DIR__ . "/tmp2.txt"; // 存在しないファイルを参照 var_dump(filesize($fileName)); /* * > Warning: filesize(): stat failed for xxx/a.php on line 10 * > bool(false) * 警告と共に失敗の false が返ってくる */ // ファイルを作成 echo "touch\n"; touch($fileName); touch($fileName2nd); // 空ファイルを参照 var_dump(filesize($fileName)); /* * > int(0) * 空ファイルであることを示す 0 が返ってくる */ // ファイルの中に書き込み echo "file_put_contents(\$fileName, 'hogehoge')\n"; file_put_contents($fileName, 'hogehoge'); // 空でなくなったファイルを参照 var_dump(filesize($fileName)); /* * > int(0) * キャッシュにより前回の結果である 0 が返ってくる */ var_dump(stat($fileName)[7]); /* * > int(0) * stat の7番インデックスにはバイト単位のサイズが格納されている * @see https://www.php.net/manual/ja/function.stat.php * キャッシュにより前回の結果である 0 が返ってくる。 * clearstatcache で管理されるキャッシュは PHP 内部で共有されている */ // ファイル関連情報のキャッシュをクリア echo "clearstatcache();\n"; clearstatcache(); // 再度、空でなくなったファイルを参照 var_dump(filesize($fileName)); /* * > int(8) * 新鮮な結果として 8byte の中身があることを示す 8 が返ってくる */ echo "ファイル削除時の挙動\n"; // tmp.txt とは別ファイルである tmpSub.txt についてキャッシュを作り、そのキャッシュが機能していることを確認 var_dump(filesize($fileName2nd)); // > int(0) file_put_contents($fileName2nd, 'hogehoge'); var_dump(filesize($fileName2nd)); // > int(0) // ファイルを削除 unlink($fileName); // 削除されたファイルを参照 var_dump(filesize($fileName)); /* * > Warning: filesize(): stat failed for xxx/a.php on line 70 * > bool(false) * unlink はキャッシュを削除するので、警告と共に失敗の false が返ってくる */ var_dump(filesize($fileName2nd)); /* * > int(8) * unlink によるキャッシュの削除は unlink 対象以外のファイルのキャッシュ削除も行う */ // 後始末 unlink($fileName2nd);
ある意味便利ですが問題に陥りやすい部分としてキャッシュのキーが関数とファイルの組み合わせでなく、ファイル名という点があります。これは C 実装の PHP のソースコードからも読み取れます。
各 stat 関数が同じ関数内で実行される関数になっているのが読み取れる部分が次リンクからの数十行で
php-src/filestat.c at 9a165336bd742b3414a165922764cd0e4e6498d3 · php/php-src#999
キャッシュのキーが filename であることがわかるのが次です。
php-src/filestat.c at 9a165336bd742b3414a165922764cd0e4e6498d3 · php/php-src#796
ドキュメントにありませんが、PHPのソースコードを見るに chroot 関数でもキャッシュの削除が行われるようです。
PHP: chroot – Manual
php-src/dir.c at 9a165336bd742b3414a165922764cd0e4e6498d3 · php/php-src#L289
ちなみに PHP 8.1.1 における clearstatcache 関数の実装が次で
php-src/filestat.c at e2a082c4a94bc62dfc1d2119d4ce3301632bdfdb · php/php-src#709
処理本体が次です。
php-src/filestat.c at e2a082c4a94bc62dfc1d2119d4ce3301632bdfdb · php/php-src#L685
/* {{{ php_clear_stat_cache() */ PHPAPI void php_clear_stat_cache(bool clear_realpath_cache, const char *filename, size_t filename_len) { /* always clear CurrentStatFile and CurrentLStatFile even if filename is not NULL * as it may contain outdated data (e.g. "nlink" for a directory when deleting a file * in this directory, as shown by lstat_stat_variation9.phpt) */ if (BG(CurrentStatFile)) { zend_string_release(BG(CurrentStatFile)); BG(CurrentStatFile) = NULL; } if (BG(CurrentLStatFile)) { zend_string_release(BG(CurrentLStatFile)); BG(CurrentLStatFile) = NULL; } if (clear_realpath_cache) { if (filename != NULL) { realpath_cache_del(filename, filename_len); } else { realpath_cache_clean(); } } } /* }}} */
ソースコードの中でたびたびphp_clear_stat_cache(1, NULL, 0);
と現れ、この NULL はファイル名問わずキャッシュ削除を指示する引数ですので、ファイル名単位の個別キャッシュはわりと気軽に消えてくれる様です。
PHPのドキュメントは 2022-01-14 に修正されました
clearstatcache の本体にある realpath と stat の違いなど見るに次のドキュメントの引用(2022-01-13 17:35時点)と異なり stat キャッシュは特定のファイル名だけ消す機能がない感じがします。まあキャッシュとして古い情報が残っていてうれしい状況がちょっと思いつかないのですし処理も早そうなので別にそうであっても問題ないですが(古い情報を永続化するならキャッシュとは別にファイルやデータベースに出力する、古い情報をプログラム内で使うならば PHP 内部ではなく自分が触れる部分である変数に保持しておく、といった形になりそうです)。
パラメータ ¶
clear_realpath_cache
realpath キャッシュをクリアするか否か。
filename
realpath キャッシュと stat キャッシュを特定のファイル名だけに対してクリアする。
clear_realpath_cache
がtrue
の場合にのみ使用。