こんにちは、あるいはこんばんは。すぱ..すぱらしいサーバサイドのエンジニアの(@taclose)です☆
今回の記事はOPcacheのpreloadが出来るようになろう! という内容です。
尚、OPcacheのpreloadの基本設定とかについては以下の記事を参考にしてください。
今回は上記記事では話していなかったpreloadのよくありそうな失敗話になります。
PHPerKaigi2023では語れなかった部分だったので是非見てもらえればと思います。
そして preloadとrequire_onceとかとの関係性について理解を深めて、更なる改善につなげてもらえればと思います!
では早速はじめていきましょう!
失敗談:preloadしても変化がない!
前回も張った画像ですが、まずこの図ですね。
この図の通りでいうなら、「お?preload使えば動的にロードなくなるんだ!」ってなるのですが、実際どうなるか?
手順は以下です。
- preloadの設定解除して
count(get_included_files())
を出力 - apacheを再起動
- preloadの設定して
count(get_included_files())
を出力
この数の差がpreloadで事前ロードが成功した数になります。
私のとある開発環境では300ファイルのinclude数が150ファイルぐらいまで減らす事ができました。
「そのpreload出来なかった150ファイルは一体なんぞや!」
これを今日は深掘っていきますよ!
解決策
説明用のファイルはこちら
今回のこの問題を説明する用に簡単なPHPファイルを用意しました。皆さんも手元で試してもらうと良いかな!
<?php # index.php require_once("autoload.php"); print_r(get_included_files()); echo "loaded : index.\n"; $a = new A(); $a->call(); print_r(get_included_files());
<?php # autoload.php spl_autoload_register( function ($className) { echo "spl_autoload: $className.\n"; require_once($className.".php"); } );
<?php # A.php class A extends AP { public static string $BName = B::name; public function call() { echo "call : ".__class__.", B#name : ".self::$BName."\n"; } }
<?php # AP.php class AP { public function call() { echo "call : ".__class__."\n"; } }
<?php # B.php class B { public const name = "B#name"; }
<?php # preload.php $root_path=dirname(__FILE__)."/"; # 以下例えばのpreload 一旦コメントアウトしておきますね。 #opcache_compile_file($root_path."AP.php"); #opcache_compile_file($root_path."A.php"); #opcache_compile_file($root_path."B.php"); #opcache_compile_file($root_path."index.php"); #opcache_compile_file($root_path."autoload.php"); echo("Preloading Success\n");
試してみよう!
まずはpreload.phpをphp.iniに設定しないでやるとどうなるでしょうか?
$ php index.php Array ( [0] => /usr/local/application/works/index.php [1] => /usr/local/application/works/autoload.php ) loaded : index. spl_autoload: A. spl_autoload: AP. spl_autoload: B. call : A, B#name : B#name Array ( [0] => /usr/local/application/works/index.php [1] => /usr/local/application/works/autoload.php [2] => /usr/local/application/works/A.php [3] => /usr/local/application/works/AP.php [4] => /usr/local/application/works/B.php )
実行時にindex.php
がロードされて、require_onceでautoload.php
がロードされて、各クラスが必要になったタイミングでspl_autoloadがされていってるんだな!っていうのがよくわかりますね。
では、preload.php
でA.php
をpreloadするとどうなるでしょうか?
$ php index.php Preloading Success Array ( [0] => /usr/local/application/works/index.php [1] => /usr/local/application/works/autoload.php ) loaded : index. spl_autoload: A. spl_autoload: AP. spl_autoload: B. call : A, B#name : B#name Array ( [0] => /usr/local/application/works/index.php [1] => /usr/local/application/works/autoload.php [2] => /usr/local/application/works/A.php [3] => /usr/local/application/works/AP.php [4] => /usr/local/application/works/B.php )
変わってないじゃないか!!!!ログをみてみると...
$ cat /var/log/messages | tail -n1 Mar 28 20:19:18 auto7-dev045 php: PHP Warning: Can't preload unlinked class A: Unknown parent AP in /usr/local/application/works/A.php on line 3
なるほど、preloadというのは親クラスとかも一緒にpreloadしてあげないといけないんですね!
よし、ではAP.php
もpreloadして再度トライ!
$ php index.php Preloading Success Array ( [0] => /usr/local/application/works/index.php [1] => /usr/local/application/works/autoload.php ) loaded : index. spl_autoload: A. spl_autoload: B. call : A, B#name : B#name Array ( [0] => /usr/local/application/works/index.php [1] => /usr/local/application/works/autoload.php [2] => /usr/local/application/works/A.php [3] => /usr/local/application/works/B.php )
おーい!もっかいログを見てみましょう!
$ cat /var/log/messages | tail -n1 Mar 28 20:27:23 auto7-dev045 php: PHP Warning: Can't preload class A with unresolved initializer for static property $BName in /usr/local/application/works/A.php on line 3
なになに、static propertyでつかってる$BNAMEがわかりませんと...
static propertyとかで使われてる値も一緒にpreloadしないとダメ! って事ですね!
よし、じゃB.php
もpreloadしましょう!再度トライだ!
$ php index.php Preloading Success Array ( [0] => /usr/local/application/works/index.php [1] => /usr/local/application/works/autoload.php ) loaded : index. call : A, B#name : B#name Array ( [0] => /usr/local/application/works/index.php [1] => /usr/local/application/works/autoload.php )
成功ですね!!!あとはautoload.php
だけか!よし、これもpreloadしましょう!
$ php index.php Preloading Success Array ( [0] => /usr/local/application/works/index.php [1] => /usr/local/application/works/autoload.php ) loaded : index. call : A, B#name : B#name Array ( [0] => /usr/local/application/works/index.php [1] => /usr/local/application/works/autoload.php )
なぜ!!ログを見てみましょう!ってログも何も出てないよ!なぜか?答えは
preloadしていてもrequire_onceとかしちゃうと結局ロードされちゃいます!!
ですので、index.phpのrequire_onceを削ってみましょう!
こんな感じです。
<?php # index.php print_r(get_included_files()); echo "loaded : index.\n"; $a = new A(); $a->call(); print_r(get_included_files());
結果は・・・・?
$ php index.php Preloading Success Array ( [0] => /usr/local/application/works/index.php ) loaded : index. call : A, B#name : B#name Array ( [0] => /usr/local/application/works/index.php )
大成功!ついにincludesファイルが0個になりました!!
まとめ
はい、まとめです。preloadって事前ロードしてくれる機能ではあるのですが、内部で使われているクラスに漏れがあると、そのクラス自体のpreloadも出来ない!
そして、require_onceなどしているものは結局preloadされてしまう!という事なんですね。
皆さんもこれを参考にしながらpreloadの設定頑張ってくださーい!!