おぎろぐはてブロ

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

include_pathを探しにstat()を呼びまくるのを眺める

id:koyhogeさんのPHPのコードキャッシュがなぜ速いのか - Blog::koyhoge::Techから。(先日はありがとうございました m(__)m)

PHPは内部でstat()を呼びまくっているので遅い」とのことですので、include_pathの後ろの方のディレクトリにあるスクリプトファイルを大量にrequireしていたりすると、それだけでコードキャッシュのメリットが出てくるのかもしれません。

PHPのコードキャッシュがなぜ速いのか - Blog::koyhoge::Tech

どのレベルからキャッシュが利用されるのかは、ちょっと記憶が無いので、コードを読み直さないとダメなのですが、「stat()を呼びまくって遅い」のstat()が爆発的に呼ばれる動きを軽く説明?したいとおもいます。(早く寝なきゃと適当に書いてるので、説明になってない。。ーー;いかんせん、こういうのあまりやらないので、、)
statコールがどのような感じに叩かれているかは、straceなんかを叩いてみれば分かります。

テストコード

例えば、以下のように、PEARディレクトリにある、System.phpをrequireするだけのコードを書いたとします。(System.phpは、PEAR.php と Console/Getopt.php をrequireします。)

<?php
require "System.php";
?>

include_path 3つ目で見つかるパターン

これで、includeパスを ".:/tmp:/usr/local/lib/php" としたときの動きをみてみます。3つ目のパスでファイルが見つかります。(今日、Ubuntuに入れてみたPHP v5.2.3 (cli)でopcodeキャッシュのたぐいが入ってない環境で検証)

$ strace -etrace=file php -d include_path=".:/tmp:/usr/local/lib/php" test.php
...
初期化のopenコールがぐだぐだと続く
...
open("test.php", O_RDONLY)                                                                = 3
getcwd("/home/stattest/tmp", 4096)                                                        = 19
lstat64("/home", {st_mode=S_IFDIR|0755, st_size=4096, ...})                               = 0
lstat64("/home/stattest", {st_mode=S_IFDIR|0755, st_size=4096, ...})                      = 0
lstat64("/home/stattest/tmp", {st_mode=S_IFDIR|0755, st_size=4096, ...})                  = 0
lstat64("/home/stattest/tmp/test.php", {st_mode=S_IFREG|0644, st_size=114, ...})          = 0
# ここからSystem.php探し
getcwd("/home/stattest/tmp", 4096)                                                        = 19
lstat64("/home", {st_mode=S_IFDIR|0755, st_size=4096, ...})                               = 0
lstat64("/home/stattest", {st_mode=S_IFDIR|0755, st_size=4096, ...})                      = 0
lstat64("/home/stattest/tmp", {st_mode=S_IFDIR|0755, st_size=4096, ...})                  = 0
lstat64("/home/stattest/tmp/System.php", 0xbfe632ec)                                      = -1 ENOENT (No such file or directory)
open("/home/stattest/tmp/System.php", O_RDONLY)                                           = -1 ENOENT (No such file or directory)
lstat64("/tmp", {st_mode=S_IFDIR|S_ISVTX|0777, st_size=4096, ...})                        = 0
lstat64("/tmp/System.php", 0xbfe632ec)                                                    = -1 ENOENT (No such file or directory)
open("/tmp/System.php", O_RDONLY)                                                         = -1 ENOENT (No such file or directory)
lstat64("/usr", {st_mode=S_IFDIR|0755, st_size=4096, ...})                                = 0
lstat64("/usr/local", {st_mode=S_IFDIR|0755, st_size=4096, ...})                          = 0
lstat64("/usr/local/lib", {st_mode=S_IFDIR|0755, st_size=4096, ...})                      = 0
lstat64("/usr/local/lib/php", {st_mode=S_IFDIR|0755, st_size=4096, ...})                  = 0
lstat64("/usr/local/lib/php/System.php", {st_mode=S_IFREG|0644, st_size=19727, ...})      = 0
open("/usr/local/lib/php/System.php", O_RDONLY)                                           = 3
# PEAR.php探し
getcwd("/home/stattest/tmp", 4096)                                                        = 19
lstat64("/home", {st_mode=S_IFDIR|0755, st_size=4096, ...})                               = 0
lstat64("/home/stattest", {st_mode=S_IFDIR|0755, st_size=4096, ...})                      = 0
lstat64("/home/stattest/tmp", {st_mode=S_IFDIR|0755, st_size=4096, ...})                  = 0
lstat64("/home/stattest/tmp/PEAR.php", 0xbfe6323c)                                        = -1 ENOENT (No such file or directory)
open("/home/stattest/tmp/PEAR.php", O_RDONLY)                                             = -1 ENOENT (No such file or directory)
lstat64("/tmp", {st_mode=S_IFDIR|S_ISVTX|0777, st_size=4096, ...})                        = 0
lstat64("/tmp/PEAR.php", 0xbfe6323c)                                                      = -1 ENOENT (No such file or directory)
open("/tmp/PEAR.php", O_RDONLY)                                                           = -1 ENOENT (No such file or directory)
lstat64("/usr", {st_mode=S_IFDIR|0755, st_size=4096, ...})                                = 0
lstat64("/usr/local", {st_mode=S_IFDIR|0755, st_size=4096, ...})                          = 0
lstat64("/usr/local/lib", {st_mode=S_IFDIR|0755, st_size=4096, ...})                      = 0
lstat64("/usr/local/lib/php", {st_mode=S_IFDIR|0755, st_size=4096, ...})                  = 0
lstat64("/usr/local/lib/php/PEAR.php", {st_mode=S_IFREG|0644, st_size=34557, ...})        = 0
open("/usr/local/lib/php/PEAR.php", O_RDONLY)                                             = 3
# Console/GetOpt.php探し
getcwd("/home/stattest/tmp", 4096)                                                        = 19
lstat64("/home", {st_mode=S_IFDIR|0755, st_size=4096, ...})                               = 0
lstat64("/home/stattest", {st_mode=S_IFDIR|0755, st_size=4096, ...})                      = 0
lstat64("/home/stattest/tmp", {st_mode=S_IFDIR|0755, st_size=4096, ...})                  = 0
lstat64("/home/stattest/tmp/Console", 0xbfe6323c)                                         = -1 ENOENT (No such file or directory)
open("/home/stattest/tmp/Console/Getopt.php", O_RDONLY)                                   = -1 ENOENT (No such file or directory)
lstat64("/tmp", {st_mode=S_IFDIR|S_ISVTX|0777, st_size=4096, ...})                        = 0
lstat64("/tmp/Console", 0xbfe6323c)                                                       = -1 ENOENT (No such file or directory)
open("/tmp/Console/Getopt.php", O_RDONLY)                                                 = -1 ENOENT (No such file or directory)
lstat64("/usr", {st_mode=S_IFDIR|0755, st_size=4096, ...})                                = 0
lstat64("/usr/local", {st_mode=S_IFDIR|0755, st_size=4096, ...})                          = 0
lstat64("/usr/local/lib", {st_mode=S_IFDIR|0755, st_size=4096, ...})                      = 0
lstat64("/usr/local/lib/php", {st_mode=S_IFDIR|0755, st_size=4096, ...})                  = 0
lstat64("/usr/local/lib/php/Console", {st_mode=S_IFDIR|0755, st_size=4096, ...})          = 0
lstat64("/usr/local/lib/php/Console/Getopt.php", {st_mode=S_IFREG|0644, st_size=10447, ...}) = 0
open("/usr/local/lib/php/Console/Getopt.php", O_RDONLY)                                   = 3
// PEAR.phpを再探索 (Console/GetOpt.phpがrequireしてるので)
getcwd("/home/stattest/tmp", 4096)                                                        = 19
open("/home/stattest/tmp/PEAR.php", O_RDONLY)                                             = -1 ENOENT (No such file or directory)
open("/tmp/PEAR.php", O_RDONLY)                                                           = -1 ENOENT (No such file or directory)
open("/usr/local/lib/php/PEAR.php", O_RDONLY)                                             = 3
Process 30416 detached

各探索で、getcwdが叩かれてるのは、include_pathの1つ目で指定している "." を展開するためです。
最後の再探索だけはどうもキャッシュがされているようで、statコールが省略されてます。PHP5では、realpathの結果をキャッシュするようになったということで、4より良くなったという話を聞いたことがあります。(あやふやですが) これのこと?

include_pathの1つ目で見つかるパターン

これで、includeパスを "/usr/local/lib/php:/tmp:." としたときの動きをみてみます。1つ目のパスでファイルが見つかるようになります。

$ strace -etrace=file -a 90 php -d include_path="/usr/local/lib/php:/tmp:." test.php
...
open("test.php", O_RDONLY)                                                                = 3
getcwd("/home/stattest/tmp", 4096)                                                        = 19
lstat64("/home", {st_mode=S_IFDIR|0755, st_size=4096, ...})                               = 0
lstat64("/home/stattest", {st_mode=S_IFDIR|0755, st_size=4096, ...})                      = 0
lstat64("/home/stattest/tmp", {st_mode=S_IFDIR|0755, st_size=4096, ...})                  = 0
lstat64("/home/stattest/tmp/test.php", {st_mode=S_IFREG|0644, st_size=114, ...})          = 0
# ここからSystem.php探し
lstat64("/usr", {st_mode=S_IFDIR|0755, st_size=4096, ...})                                = 0
lstat64("/usr/local", {st_mode=S_IFDIR|0755, st_size=4096, ...})                          = 0
lstat64("/usr/local/lib", {st_mode=S_IFDIR|0755, st_size=4096, ...})                      = 0
lstat64("/usr/local/lib/php", {st_mode=S_IFDIR|0755, st_size=4096, ...})                  = 0
lstat64("/usr/local/lib/php/System.php", {st_mode=S_IFREG|0644, st_size=19727, ...})      = 0
open("/usr/local/lib/php/System.php", O_RDONLY)                                           = 3
# PEAR.php探し
lstat64("/usr", {st_mode=S_IFDIR|0755, st_size=4096, ...})                                = 0
lstat64("/usr/local", {st_mode=S_IFDIR|0755, st_size=4096, ...})                          = 0
lstat64("/usr/local/lib", {st_mode=S_IFDIR|0755, st_size=4096, ...})                      = 0
lstat64("/usr/local/lib/php", {st_mode=S_IFDIR|0755, st_size=4096, ...})                  = 0
lstat64("/usr/local/lib/php/PEAR.php", {st_mode=S_IFREG|0644, st_size=34557, ...})        = 0
open("/usr/local/lib/php/PEAR.php", O_RDONLY)                                             = 3
# Console/GetOpt.php探し
lstat64("/usr", {st_mode=S_IFDIR|0755, st_size=4096, ...})                                = 0
lstat64("/usr/local", {st_mode=S_IFDIR|0755, st_size=4096, ...})                          = 0
lstat64("/usr/local/lib", {st_mode=S_IFDIR|0755, st_size=4096, ...})                      = 0
lstat64("/usr/local/lib/php", {st_mode=S_IFDIR|0755, st_size=4096, ...})                  = 0
lstat64("/usr/local/lib/php/Console", {st_mode=S_IFDIR|0755, st_size=4096, ...})          = 0
lstat64("/usr/local/lib/php/Console/Getopt.php", {st_mode=S_IFREG|0644, st_size=10447, ...}) = 0
open("/usr/local/lib/php/Console/Getopt.php", O_RDONLY)                                   = 3
// PEAR.phpを再探索 (Console/GetOpt.phpがrequireしてるので)
open("/usr/local/lib/php/PEAR.php", O_RDONLY)                                             = 3
Process 30597 detached

だいぶ、短くなっているのが分かると思います。PEARディレクトリの絶対パスをもっと短いところにすれば、見て分かるように、もっと数が減ります。(だからといって、パスを短くするようなことは嫌ですが)

大まかな動き