PHP 7.4 では、コードのパフォーマンスを大幅に向上させる機能であるプリロードのサポートが追加されました。
簡単に言うと、その仕組みは次のとおりです。
# ファイルをプリロードするには、カスタム PHP スクリプトを記述する必要があります
## このスクリプトは、サーバー 1 回実行##● すべてのプリロードされたファイルはすべてのリクエストに対してメモリ内で使用可能
##● プリロードされたファイルへの変更は、サーバーが再起動されるまで影響を与えませんそれを理解しましょう深さ。#Opcache
プリロードは opcache の上に構築されていますが、まったく同じではありません。 Opcache は、PHP ソース ファイルを取得し、それらを「オペコード」にコンパイルし、コンパイルされたファイルをディスクに保存します。
オペコードは、実行時に簡単に解釈できるコードの低レベル表現として考えることができます。したがって、opcache は、ソース ファイルと、実行時に PHP インタープリタが実際に必要とするファイルとの間の変換ステップをスキップします。大勝利! しかし、私たちが得るものはまだたくさんあります。 Opcached ファイルは他のファイルについては認識しません。クラス A がクラス B から拡張された場合でも、実行時にそれらをリンクする必要があります。さらに、opcache はソース ファイルが変更されているかどうかのチェックを実行し、これに基づいてキャッシュを無効にします。 したがって、ここでプリロードが役立ちます。プリロードは、ソース ファイルをオペコードにコンパイルするだけでなく、関連するクラス、特性、インターフェイスをリンクします。次に、この「コンパイルされた」実行可能コード (つまり、PHP インタープリターが使用できるコード) の blob をメモリに保存します。 これで、リクエストがサーバーに到着すると、オーバーヘッドを発生させることなく、すでにメモリにロードされているコード ベースの部分を使用できるようになります。 それでは、「コード ベースの一部」とは何を意味するのでしょうか?# 実際のプリロード
プリロードするには、開発者がどのファイルをロードするかをサーバーに指示する必要があります。これは単純な PHP スクリプトで実行でき、特に難しいことは何もありませんでした。
ルールは単純です:#● プリロード スクリプトを提供し、opcache.preload コマンドを使用してそれを php.ini ファイルにリンクします。
# プリロードするすべての PHP ファイルは、opcache_compile_file() に渡すか、プリロード スクリプト内で 1 回だけ渡す必要があります。
Laravel などのフレームワークをプリロードするとします。スクリプトは、vendor/laravel ディレクトリ内のすべての PHP ファイルを調べて、それらを次々に追加する必要があります。
次のように php.ini でこのスクリプトにリンクします:
opcache.preload=/path/to/project/preload.php
これはダミー実装です:
$files = /* An array of files you want to preload */; foreach ($files as $file) { opcache_compile_file($file); }
# 警告: リンクされたクラスをプリロードできません
など、警告があります。ファイルをプリロードするには、その依存関係 (インターフェイス、特性、親クラス) もプリロードする必要があります。クラスの依存関係に問題がある場合は、サーバーの起動時に通知されます。
Can't preload unlinked class Illuminate\Database\Query\JoinClause: Unknown parent Illuminate\Database\Query\Builder
$files = /* All files in eg. vendor/laravel */; foreach ($files as $file) { require_once($file); }
class Preloader { private array $ignores = []; private static int $count = 0; private array $paths; private array $fileMap; public function __construct(string ...$paths) { $this->paths = $paths; // We'll use composer's classmap // to easily find which classes to autoload, // based on their filename $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 { // We'll loop over all registered paths // and load them one by one foreach ($this->paths as $path) { $this->loadPath(rtrim($path, '/')); } $count = self::$count; echo "[Preloader] Preloaded {$count} classes" . PHP_EOL; } private function loadPath(string $path): void { // If the current path is a directory, // we'll load all files in it if (is_dir($path)) { $this->loadDir($path); return; } // Otherwise we'll just load this one file $this->loadFile($path); } private function loadDir(string $path): void { $handle = opendir($path); // We'll loop over all files and directories // in the current path, // and load them one by one while ($file = readdir($handle)) { if (in_array($file, ['.', '..'])) { continue; } $this->loadPath("{$path}/{$file}"); } closedir($handle); } private function loadFile(string $path): void { // We resolve the classname from composer's autoload mapping $class = $this->fileMap[$path] ?? null; // And use it to make sure the class shouldn't be ignored if ($this->shouldIgnore($class)) { return; } // Finally we require the path, // causing all its dependencies to be loaded as well 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; } }
// … (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 というキーがあり、すべてのプリロードされた関数、クラス、スクリプトと、プリロードされたファイルによって消費されるメモリが一覧表示されます。# Composer サポート
有望な機能の 1 つは、ほとんどの最新の PHP プロジェクトですでに使用されている Composer ベースの自動プリロード ソリューションです。プリロード ファイルを生成するプリロード設定オプションをcomposer.jsonに追加する作業が進められています! 現在、この機能はまだ開発中ですが、ここでフォローできます。#サーバー要件
プリロードを使用する場合、devops に関して言及すべき重要な点がさらに 2 つあります。すでにご存知のとおり、プリロード用に php.ini にエントリを指定する必要があります。これは、共有ホスティングを使用する場合、PHP を自由に設定できないことを意味します。実際には、プリロードされたファイルを個々のプロジェクトに最適化できるようにするには、専用の (仮想) サーバーが必要です。これを覚えて。
また、メモリ ファイルをリロードする必要があるたびにサーバーを再起動する必要があることにも注意してください (php-fpm を使用する場合はこれで十分です)。これはほとんどの人にとって明らかなことのように思えるかもしれませんが、それでも言及する価値があります。
#パフォーマンス
次に最も重要な質問: プリロードは本当にパフォーマンスを向上させますか?
答えは「はい」です: Ben Morel による共有: 以下はいくつかのベンチマークは、前にリンクした同じコンポーザーの質問にあります。
興味深いことに、コード ベースで頻繁に使用されるクラスである「ホット クラス」のみをプリロードすることを決定できます。 Ben のベンチマークは、人気のあるクラスを約 100 個だけロードする方が、実際にはすべてのクラスをプリロードするよりもパフォーマンスが向上することを示しています。これは、13% と 17% のパフォーマンス向上の差です。
もちろん、どのクラスをプリロードする必要があるかは、特定のプロジェクトによって異なります。最初にできるだけ多くのプリロードを行うことが賢明です。わずかな割合の増加が必要な場合は、実行時にコードを監視する必要があります。
もちろん、この作業はすべて自動化することができ、将来的には可能になるかもしれません。
ここで、覚えておくべき最も重要なことは、composer はプリロード ファイルを自分で作成する必要がないようにサポートを追加するということです。また、この機能をサーバーにセットアップするのは簡単です。あなたはそれを完全に制御できます。
翻訳: https://stitcher.io/blog/preloading-in-php-74
以上がPHP 7.4 のプリロード (Opcache プリロード)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。