(jp) =
PHP 7.4 では、プリロードのサポートが追加されました。これは、コードのパフォーマンスを大幅に向上させる機能です。
一言で言えば、これはそれがどのように機能するかです:
- ファイルをプリロードするには、カスタム PHP スクリプトを作成する必要があります
- このスクリプトは、サーバーの起動時に 1 回実行されます
- プリロードされたすべてのファイルは、すべてのリクエストに対してメモリ内で利用可能
- サーバーが再起動されるまで、プリロードされたファイルに加えられた変更は効果がありません
詳しく見てみましょう。
# Opcache、しかしそれ以上
プリロードは opcache の上に構築されていますが、まったく同じではありません。 Opcache は、PHP ソース ファイルを受け取り、それを「オペコード」にコンパイルし、それらのコンパイル済みファイルをディスクに保存します。
オペコードは、実行時に簡単に解釈できるコードの低レベル表現と考えることができます。 したがって、opcache は、ソース ファイルと実行時に PHP インタープリターが実際に必要とするファイルとの間の変換ステップをスキップします。 大勝利!
しかし、それ以上に得られるものがあります。 opcached ファイルは他のファイルを認識しません。 クラスがあれば A
クラスからの拡張 B
、実行時にそれらをリンクする必要があります。 さらに、opcache は、ソース ファイルが変更されたかどうかをチェックし、それに基づいてキャッシュを無効にします。
したがって、ここでプリロードの出番です。プリロードは、ソース ファイルをオペコードにコンパイルするだけでなく、関連するクラス、トレイト、およびインターフェイスを一緒にリンクします。 次に、この「コンパイル済み」の実行可能なコードの塊 (つまり、PHP インタープリターが使用できるコード) をメモリに保持します。
要求がサーバーに到着すると、オーバーヘッドなしで、既にメモリにロードされているコードベースの部分を使用できるようになりました。
では、「コードベースのどの部分」について話しているのでしょうか?
# 実際のプリロード
プリロードを機能させるには、開発者がサーバーにどのファイルをロードするかを指示する必要があります。 これは単純な PHP スクリプトで実行され、実際に難しいことは何もありません。
ルールは簡単です:
- プリロード スクリプトを提供し、php.ini ファイルにリンクします。
opcache.preload
- プリロードしたいすべての PHP ファイルを に渡す必要があります
opcache_compile_file()
または、プリロード スクリプト内から 1 回要求される
たとえば、Laravel などのフレームワークをプリロードしたいとします。 スクリプトは、すべての PHP ファイルをループする必要があります。 vendor/laravel
ディレクトリに移動し、それらを 1 つずつ含めます。
php.ini でこのスクリプトにリンクする方法は次のとおりです。
opcache.preload=/path/to/project/preload.php
そして、ここにダミーの実装があります:
$files = ;
foreach ($files as $file)
opcache_compile_file($file);
# 警告: リンクされていないクラスをプリロードできません
ただし、注意事項があります。 ファイルをプリロードするには、その依存関係 (インターフェース、トレイト、親クラス) もプリロードする必要があります。
クラスの依存関係に問題がある場合は、サーバーの起動時に通知されます。
Can't preload unlinked class
Illuminate\Database\Query\JoinClause:
Unknown parent
Illuminate\Database\Query\Builder
見る、 opcache_compile_file()
ファイルを解析しますが、実行しません。 これは、クラスにプリロードされていない依存関係がある場合、それ自体もプリロードできないことを意味します。
これは致命的な問題ではありません。サーバーは正常に動作します。 しかし、実際に必要なすべてのプリロード ファイルがあるわけではありません。
幸いなことに、リンクされたファイルも確実にロードされるようにする方法があります。 opcache_compile_file
あなたが使用することができます require_once
、登録されたオートローダー (おそらく作曲家のもの) に残りの処理を任せます。
$files = ;
foreach ($files as $file)
require_once($file);
まだいくつかの注意事項があります。 たとえば、Laravel をプリロードしようとしている場合、フレームワーク内には、まだ存在しない他のクラスに依存するクラスがいくつかあります。 たとえば、ファイルシステム キャッシュ クラス \Illuminate\Filesystem\Cache
に依存している \League\Flysystem\Cached\Storage\AbstractCache
ファイルシステム キャッシュを使用していない場合、プロジェクトにインストールされない可能性があります。
すべてをプリロードしようとすると、「クラスが見つかりません」というエラーが発生する場合があります。 幸いなことに、デフォルトの Laravel インストールでは、これらのクラスはほんの一握りしかなく、簡単に無視できます。 便宜上、ファイルを無視しやすくするための小さなプリローダー クラスを作成しました。これは次のようになります。
class Preloader
private array $ignores = [];
private static int $count = 0;
private array $paths;
private array $fileMap;
public function __construct(string ...$paths)
$this->paths = $paths;
$classMap = require __DIR__ . '/vendor/composer/autoload_classmap.php';
$this->fileMap = array_flip($classMap);
public function paths(string ...$paths): Preloader
$this->paths = array_merge(
$this->paths,
$paths
);
return $this;
public function ignore(string ...$names): Preloader
$this->ignores = array_merge(
$this->ignores,
$names
);
return $this;
public function load(): void
foreach ($this->paths as $path)
$this->loadPath(rtrim($path, "https://stitcher.io/"));
$count = self::$count;
echo "[Preloader] Preloaded $count classes" . PHP_EOL;
private function loadPath(string $path): void
if (is_dir($path))
$this->loadDir($path);
return;
$this->loadFile($path);
private function loadDir(string $path): void
$handle = opendir($path);
while ($file = readdir($handle))
if (in_array($file, ['.', '..']))
continue;
$this->loadPath("$path/$file");
closedir($handle);
private function loadFile(string $path): void
$class = $this->fileMap[$path] ?? null;
if ($this->shouldIgnore($class))
return;
require_once($path);
self::$count++;
echo "[Preloader] Preloaded `$class`" . PHP_EOL;
private function shouldIgnore(?string $name): bool
if ($name === null)
return true;
foreach ($this->ignores as $ignore)
if (strpos($name, $ignore) === 0)
return true;
return false;
このクラスを同じプリロード スクリプトに追加することで、次のように Laravel フレームワーク全体をロードできるようになりました。
(new Preloader())
->paths(__DIR__ . '/vendor/laravel')
->ignore(
\Illuminate\Filesystem\Cache::class,
\Illuminate\Log\LogManager::class,
\Illuminate\Http\Testing\File::class,
\Illuminate\Http\UploadedFile::class,
\Illuminate\Support\Carbon::class,
)
->load();
# 効く?
もちろん、これが最も重要な質問です。すべてのファイルが正しくロードされましたか? サーバーを再起動するだけで簡単にテストでき、次の出力をダンプできます。 opcache_get_status()
PHPスクリプトで。 と呼ばれるキーがあることがわかります preload_statistics
、プリロードされたすべての関数、クラス、およびスクリプトを一覧表示します。 プリロードされたファイルによって消費されるメモリと同様に。
# 作曲家のサポート
有望な機能の 1 つはおそらく、composer に基づく自動プリロード ソリューションです。これは、現在のほとんどの PHP プロジェクトで既に使用されています。 人々はプリロード構成オプションを追加するために取り組んでいます composer.json
これにより、プリロード ファイルが生成されます。 現時点では、この機能はまだ開発中ですが、ここでフォローできます。
2019-11-29 更新: ジョルディの回答でわかるように、作曲家のサポートが停止しました。
# サーバー要件
プリロードを使用する場合、devops 側について言及する重要なことがさらに 2 つあります。
プリロードを機能させるには、php.ini でエントリを指定する必要があることは既にご存じでしょう。 つまり、共有ホスティングを使用している場合、自由に PHP を構成することはできません。 実際には、単一のプロジェクト用にプリロードされたファイルを最適化できるようにするには、専用の (仮想) サーバーが必要です。 ですから、それを覚えておいてください。
また、サーバーを再起動する必要があることにも注意してください (php-fpm
使用している場合は十分です) メモリ内ファイルをリロードするたびに。 これはほとんどの人にとって当たり前のように思えるかもしれませんが、それでも言及する価値があります。
# パフォーマンス
ここで、最も重要な質問に移ります: プリロードは実際にパフォーマンスを向上させますか?
もちろん、答えはイエスです。Ben Morel がいくつかのベンチマークを共有しました。これは、以前にリンクされた同じ composer issue で見つけることができます。 また、実際の Laravel プロジェクト内で独自のベンチマークも行いました。 それらについてはこちらで読むことができます。
興味深いことに、コードベースで頻繁に使用される「ホット クラス」のみをプリロードすることもできます。 Ben のベンチマークでは、約 100 個のホット クラスのみをロードするだけで、実際にはすべてをプリロードするよりも優れたパフォーマンスが得られることが示されています。 13% と 17% のパフォーマンス向上の違いです。
もちろん、どのクラスをプリロードする必要があるかは、特定のプロジェクトに依存します。 最初はできるだけプリロードするのが賢明です。 数パーセントの増加が本当に必要な場合は、実行中にコードを監視する必要があります。
もちろん、これはすべて自動化することもできます。おそらく将来的にはそうなるでしょう。
今のところ、覚えておくべき最も重要なことは、composer がサポートを追加するため、自分でプリロード ファイルを作成する必要がないことと、この機能を完全に制御できることを考えると、サーバー上でこの機能を非常に簡単にセットアップできることです。 .
PHP 7.4 がリリースされたら、プリロードを使用しますか? この記事を読んで何か感想や感想はありませんか? 経由でお知らせください ツイッター または電子メール。