Heim  >  Artikel  >  Backend-Entwicklung  >  Vorladen (Opcache Preloading) in PHP 7.4

Vorladen (Opcache Preloading) in PHP 7.4

藏色散人
藏色散人Original
2019-11-30 14:04:457170Durchsuche

In PHP 7.4 wurde Unterstützung für das Vorladen hinzugefügt, eine Funktion, die die Leistung Ihres Codes erheblich verbessern kann.

Kurz gesagt funktioniert es so:

● Um die Datei vorab zu laden, müssen Sie ein benutzerdefiniertes PHP-Skript schreiben

● Dieses Skript beginnt auf der Server Wird einmal ausgeführt

● Alle vorinstallierten Dateien sind im Speicher für alle Anfragen verfügbar

● Änderungen an vorinstallierten Dateien haben keine Auswirkungen, bis der Server neu gestartet wird

Lassen Sie uns einen genaueren Blick darauf werfen Schau es dir an.

#Opcache

Obwohl das Vorladen auf dem Opcache aufbaut, ist es nicht genau dasselbe. Opcache nimmt Ihre PHP-Quelldateien, kompiliert sie in „Opcodes“ und speichert diese kompilierten Dateien dann auf der Festplatte.

Sie können sich Opcodes als Low-Level-Darstellungen von Code vorstellen, die zur Laufzeit leicht interpretiert werden können. Daher überspringt opcache den Konvertierungsschritt zwischen der Quelldatei und dem, was der PHP-Interpreter zur Laufzeit tatsächlich benötigt. Ein riesiger Sieg!

Aber wir haben noch mehr zu gewinnen. Opcached-Dateien wissen nichts über andere Dateien. Wenn Klasse A von Klasse B ausgeht, müssen sie zur Laufzeit noch miteinander verknüpft werden. Darüber hinaus führt opcache eine Prüfung durch, um festzustellen, ob die Quelldatei geändert wurde, und macht auf dieser Grundlage den Cache ungültig.

Hier kommt also das Vorladen ins Spiel: Es kompiliert nicht nur Quelldateien in Opcodes, sondern verknüpft auch verwandte Klassen, Merkmale und Schnittstellen miteinander. Anschließend wird dieser „kompilierte“ Blob aus ausführbarem Code (d. h. Code, den der PHP-Interpreter verwenden kann) im Speicher gespeichert.

Wenn nun eine Anfrage den Server erreicht, kann dieser Teile der Codebasis verwenden, die bereits in den Speicher geladen sind, ohne dass ein Overhead entsteht.

Was meinen wir also mit „Teil der Codebasis“?

# Vorladen in der Praxis

Um vorab zu laden, muss der Entwickler Teilen Sie dem Server mit, welche Dateien geladen werden sollen. Dies geschah mit einem einfachen PHP-Skript und es war wirklich nichts Schwieriges dabei.

Die Regeln sind einfach:

● Sie stellen ein Preload-Skript bereit und verknüpfen es mit dem Befehl opcache.preload in Ihre php.ini-Datei.

● Jede PHP-Datei, die Sie vorab laden möchten, sollte an opcache_compile_file() übergeben werden, oder nur einmal im Vorladeskript.

Angenommen, Sie möchten ein Framework wie Laravel vorab laden. Ihr Skript muss alle PHP-Dateien im Verzeichnis „vendor/laravel“ durchgehen und sie nacheinander hinzufügen.

Link zu diesem Skript in php.ini wie folgt:

opcache.preload=/path/to/project/preload.php

Dies ist eine Dummy-Implementierung:

$files = /* An array of files you want to preload */;
foreach ($files as $file) {
    opcache_compile_file($file);
}

# WARNUNG: Nicht verwendete verknüpfte Klassen können nicht vorab geladen werden

usw., es gibt eine Warnung! Um Dateien vorab zu laden, müssen auch ihre Abhängigkeiten (Schnittstellen, Merkmale und übergeordnete Klassen) vorab geladen werden.

Wenn es Probleme mit Klassenabhängigkeiten gibt, werden Sie beim Serverstart benachrichtigt:

Can't preload unlinked class 
Illuminate\Database\Query\JoinClause: 
Unknown parent 
Illuminate\Database\Query\Builder

Sehen Sie, opcache_compile_file() analysiert eine Datei, führt sie jedoch nicht aus. Das bedeutet, dass eine Klasse nicht selbst vorgeladen werden kann, wenn sie über nicht vorgeladene Abhängigkeiten verfügt.

Dies ist kein schwerwiegendes Problem und Ihr Server wird einwandfrei funktionieren. Sie erhalten jedoch nicht alle gewünschten vorinstallierten Dateien.

Glücklicherweise gibt es eine Möglichkeit sicherzustellen, dass die verknüpfte Datei auch geladen wird: Sie können require_once anstelle von opcache_compile_file verwenden und den registrierten Autoloader (wahrscheinlich den des Composers) den Rest erledigen lassen.

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

Es gibt noch ein paar Dinge zu beachten. Wenn Sie beispielsweise versuchen, Laravel vorab zu laden, hängen einige Klassen im Framework von anderen Klassen ab, die noch nicht vorhanden sind. Beispielsweise hängt die Dateisystem-Cache-Klasse, die den Dateisystem-Cache beleuchtet, von LeagueFlysystemCachedStorageAbstractCache ab. Wenn Sie den Dateisystem-Cache noch nie verwendet haben, können Sie ihn möglicherweise nicht in Ihrem Projekt installieren.

Beim Versuch, alles vorab zu laden, kann die Fehlermeldung „Klasse nicht gefunden“ auftreten. Glücklicherweise gibt es in einer Standardinstallation von Laravel nur wenige dieser Klassen und können leicht ignoriert werden. Der Einfachheit halber habe ich eine kleine Preloader-Klasse geschrieben, um das Ignorieren von Dateien zu erleichtern, etwa so:

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;
    }
}

Indem wir diese Klasse im selben Preload-Skript hinzufügen, können wir jetzt wie folgt laden. Führt das gesamte Laravel-Framework aus:

// …
(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();

# Arbeit?

Das ist natürlich die wichtigste Frage: Werden alle Dateien korrekt geladen? Sie können dies einfach testen, indem Sie den Server neu starten und dann die Ausgabe von opcache_get_status() in ein PHP-Skript übertragen. Sie werden sehen, dass es einen Schlüssel namens preload_statistics gibt, der alle vorinstallierten Funktionen, Klassen und Skripte zusammen mit dem von den vorinstallierten Dateien verbrauchten Speicher auflistet.

# Composer-Unterstützung

Ein vielversprechendes Feature könnte die Composer-basierte Auto-Preloading-Lösung sein, die bereits von den meisten modernen PHP-Projekten verwendet wird. Es wird daran gearbeitet, in Composer.json eine Preload-Konfigurationsoption hinzuzufügen, die die Preload-Dateien für Sie generiert. Derzeit befindet sich diese Funktion noch in der Entwicklung, aber Sie können sie hier verfolgen.

# Serveranforderungen

Es gibt zwei weitere wichtige Dinge, die in Bezug auf Entwickler bei der Verwendung des Vorladens erwähnt werden müssen.

Wie Sie bereits wissen, müssen Sie zum Vorladen einen Eintrag in der php.ini angeben. Das bedeutet, dass Sie PHP nicht frei konfigurieren können, wenn Sie Shared Hosting nutzen. In der Praxis benötigen Sie einen dedizierten (virtuellen) Server, um vorinstallierte Dateien für einzelne Projekte optimieren zu können. Denken Sie daran.

Denken Sie auch daran, dass Sie den Server jedes Mal neu starten müssen, wenn Sie die Speicherdatei neu laden müssen (dies reicht aus, wenn Sie php-fpm verwenden). Dies mag für die meisten Menschen offensichtlich erscheinen, ist aber dennoch erwähnenswert.

#Leistung

Nun zur wichtigsten Frage: Verbessert das Vorladen wirklich die Leistung

Die Antwort lautet: Ja: Geteilt von Ben Morel. Hier sind einige Benchmarks, die in derselben zuvor verlinkten Komponistenfrage zu finden sind.

Interessanterweise können Sie entscheiden, nur „Hot Classes“ vorab zu laden, also Klassen, die häufig in Ihrer Codebasis verwendet werden. Bens Benchmarks zeigen, dass das Laden von nur etwa 100 beliebten Klassen tatsächlich zu besseren Leistungssteigerungen führt als das Vorladen aller Klassen. Dies ist der Unterschied zwischen einer Leistungssteigerung von 13 % und einer Leistungssteigerung von 17 %.

Welche Klassen vorinstalliert werden sollten, hängt natürlich von Ihrem spezifischen Projekt ab. Es ist ratsam, zu Beginn so viel wie möglich vorzuladen. Wenn Sie eine kleine prozentuale Erhöhung benötigen, müssen Sie Ihren Code zur Laufzeit überwachen.

Natürlich kann all diese Arbeit automatisiert werden und ist möglicherweise in Zukunft möglich.

Das Wichtigste, was Sie sich jetzt merken sollten, ist, dass Composer Unterstützung hinzufügt, sodass Sie die Preload-Dateien nicht selbst erstellen müssen, und es so lange einfach sein wird, diese Funktion auf Ihrem Server einzurichten Sie haben die volle Kontrolle darüber.

Übersetzung: https://stitcher.io/blog/preloading-in-php-74

Das obige ist der detaillierte Inhalt vonVorladen (Opcache Preloading) in PHP 7.4. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn