PHP Conference Nagoya 2025

コードの事前ロード

PHP 7.4.0 以降では、エンジンの起動時に opcache に事前ロードするスクリプトを指定できるようになりました。 指定されたファイルに存在するあらゆる 関数、クラス、 インターフェイス や トレイト (定数は除く) は、 明示的にインクルードすることなく全てのリクエストからグローバルに利用できるようになります。 これにより(コードが常に利用できるようになるため)、メモリ使用量と、 パフォーマンスおよび便利さのトレードオフが発生します。 事前ロードされたスクリプトをクリアするには PHP プロセスの再起動が必要です。 つまり、この機能は本番環境でのみ役に立ちます。開発環境では役に立ちません。

パフォーマンスとメモリ使用量の最適なトレードオフは、アプリケーションによって異なることに注意して下さい。 "全てをあらかじめ読み込む" ことはもっとも簡単な戦略かもしれませんが、 必ずしも最適とは限りません。 さらに、コードの事前ロードは、リクエストが終了しても継続して生き残るプロセスの場合にだけ役に立ちます。 つまり、opcache が有効になった CLI スクリプトは動作はしますが、 一般的にコードの事前ロードは役に立ちません。 但し、FFI 経由でコードの事前ロードを使う場合は例外です。

注意:

コードの事前ロードは、Windows ではサポートされていません。

コードの事前ロードを設定するには、2つのステップが必要です。 まず、opcache を有効にしなければなりません。 その上で、opcache.preload の値を php.ini に設定します。

opcache.preload=preload.php

preload.php は、サーバーの起動時(PHP-FPM, mod_php, など) に一度だけ実行され、リクエストを越えて生き残るメモリ領域に読み込まれる任意のファイルです。 特権のないシステムユーザに切り替える前に root で起動するサーバーの場合や、 PHP が root 権限で実行(推奨されません)される場合のために、 opcache.preload_user を使って事前ロードを実行するためのシステムユーザーを指定することが出来ます。 デフォルトでは、事前ロードを root で行うことは禁止されていますが、 明示的に opcache.preload_user=root と指定した場合は許可されます。

preload.php スクリプトでは、 include, include_once, require, require_once, opcache_compile_file() で参照されるあらゆるファイルが評価され、 リクエストを越えて生き残るメモリ領域に読み込まれます。 次の例では、src ディレクトリにある全ての .php ファイルが事前ロードされます。 但し、Test ファイルの場合を除きます。

<?php
$directory
= new RecursiveDirectoryIterator(__DIR__ . '/src');
$fullTree = new RecursiveIteratorIterator($directory);
$phpFiles = new RegexIterator($fullTree, '/.+((?<!Test)+\.php$)/i', RecursiveRegexIterator::GET_MATCH);

foreach (
$phpFiles as $key => $file) {
require_once
$file[0];
}
?>

includeopcache_compile_file() 両方が動作しますが、コードをどう扱うかが異なります。

  • include は、ファイル内でコードを実行しますが、 opcache_compile_file() は実行しません。 これは、前者のみが条件付きの宣言 (if ブロック内の関数宣言)をサポートしているということです。
  • include はコードを実行するので、 ネストして include されたファイルも評価され、 それに含まれる宣言も事前ロードされます。
  • opcache_compile_file() は任意の順番でファイルを読み込むことが出来ます。 つまり、a.php が クラス A を定義しており、 b.phpA を継承したクラス B を定義している場合、 opcache_compile_file() はそれらふたつのファイルを任意の順番で読み込めます。 しかし、include を使う場合、 a.php必ず 最初にインクルードしなければなりません。
  • どちらの場合も、後に読み込まれるスクリプトが、 既に事前ロードされているファイルをインクルードしていれば、 スクリプトの内容は実行されます。しかし、そこで定義されているシンボルは再定義されません。 include_once を使っても、 ファイルが二度インクルードされることを妨げません。 ファイルで定義されたグローバルな定数をインクルードするために、 ファイルを再読み込みする必要があるかもしれません。 なぜなら、定数は事前ロードでは処理されないからです。
どちらのアプローチが優れているかは、あなたがどのような挙動を望むか次第です。 オートローディング と一緒に使うことで、 opcache_compile_file() は大きな柔軟性を得られます。 一方で、手動でコードをロードする場合、 include を使うほうが堅牢になるでしょう。

add a note

User Contributed Notes 2 notes

up
2
postmaster at greg0ire dot fr
2 years ago
There are caveats when enabling preloading, one of them being that it should be enabled via a php.ini file. Enabling it with a php-fpm pool configuration won't work, for instance, since preloading is global and not per-pool. To make sure that you successfully enabled preloading, you should check for a preload_statistics key in the output of opcache_get_status(). There should already be an opcache_statistics key, but that's something else entirely.
up
2
postmaster at greg0ire dot fr
2 years ago
PHP 8.1 comes with an inheritance cache that partially overlaps with what the preloading already does. If you enabled preloading on lower versions then migrated to PHP 8.1, you might want to turn off preloading and see if that comes with a performance penalty or not.
To Top