ホームページ  >  記事  >  バックエンド開発  >  PHP 7.4 のプリロード (Opcache プリロード)

PHP 7.4 のプリロード (Opcache プリロード)

藏色散人
藏色散人オリジナル
2019-11-30 14:04:457170ブラウズ

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

ご覧のとおり、 opcache_compile_file() はファイルを解析しますが、実行はしません。これは、クラスにプリロードされていない依存関係がある場合、そのクラス自体をプリロードすることはできないことを意味します。

これは致命的な問題ではなく、サーバーは正常に動作します。ただし、必要なすべてのプリロード ファイルを入手できるわけではありません。

幸いなことに、リンクされたファイルも確実にロードされるようにする方法があります。opcache_compile_file の代わりに require_once を使用し、登録されたオートローダー (おそらく Composer のもの) に残りの処理を任せることができます。

$files = /* All files in eg. vendor/laravel */;
foreach ($files as $file) {
    require_once($file);
}

注意すべき点がまだいくつかあります。たとえば、Laravel をプリロードしようとすると、フレームワーク内の一部のクラスは、まだ存在しない他のクラスに依存します。たとえば、ファイル システム キャッシュ クラス \lighting\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;
        // 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;
    }
}

このクラスを同じプリロード スクリプトに追加することで、次のように 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 というキーがあり、すべてのプリロードされた関数、クラス、スクリプトと、プリロードされたファイルによって消費されるメモリが一覧表示されます。

# 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 サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。