PHP 7.4中的预加载(Opcache Preloading)-PHP7

资源魔 47 0
正在PHP 7.4中,增加了对预加载的支持,这是一个能够明显进步代码功能的特点。

简而言之,这是它的工作形式:

● 为了预加载文件,您需求编写一个自界说PHP剧本

● 该剧本正在效劳器启动时执行一次

● 一切预加载的文件正在内存中均可用于一切申请

● 正在从新启动效劳器以前,对预加载文件所做的更改没有会孕育发生任何影响

让咱们深化理解它。

#Opcache

尽管预加载是建设正在opcache之上的,但它并非齐全同样的。Opcache将猎取您的PHP源文件,将其编译为“ opcodes”,而后将这些编译后的文件存储正在磁盘上。

您能够将操作码看做是代码的底层示意,正在运转时很容易诠释。因而,opcache会跳过源文件以及PHP诠释器正在运转时实际需求之间的转换步骤。微小的成功!

但咱们另有更多的播种。Opcached文件没有晓得其余文件。假如类a是从类B扩大而来的,那末依然需求正在运转时将它们链接正在一同。别的,opcache执行反省以查看源文件能否被修正,并将基于此使其缓存生效。

因而,这就是预加载施展作用之处:它不只将源文件编译为操作码,并且还将相干的类、特色以及接口链接正在一同。而后,它将这个“已编译”的可运转代码blob(即:PHP诠释器能够应用的代码)保留正在内存中。

如今,当申请抵达效劳器时,它能够应用曾经加载到内存中的局部代码库,而没有会孕育发生任何开支。

那末,咱们所说的“代码库的一局部”是甚么呢?

#理论中的预加载

为了进行预加载,开发职员必需奉告效劳器要加载哪些文件。这是用一个简略的PHP剧本实现的,的确不甚么艰难。

规定很简略:

● 您提供一个预加载剧本,并应用opcache.preload饬令将其链接到您的php.ini文件中。

● 您要预加载的每一个PHP文件都应该通报到opcache_compile_file(),或许正在预加载剧本中只要要一次。

假定您想要预加载一个框架,例如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()将解析一个文件,但没有执行它。这象征着假如一个类有未预加载的依赖项,它自身也不克不及预加载。

这没有是一个致命的成绩,您的效劳器能够失常工作。但你没有会失去一切你想要的预加载文件。

侥幸的是,另有一种确保链接文件也被加载的办法:您能够应用require_once替代opcache_compile_file,让已注册的autoloader(多是composer的)担任其他的工作。

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

另有一些需求留意之处。例如,假如您试图预加载Laravel,那末框架中的一些类依赖于其余尚没有存正在的类。例如,文件零碎缓存类\ lighting \ filesystem \ cache依赖于\League\Flysystem\Cached\Storage\AbstractCache,假如您从未应用过文件零碎缓存,则可能无奈将其装置到您的名目中。

测验考试预加载一切内容时,您可能会遇到“class not found”谬误。侥幸的是,正在默许的Laravel装置中,只有多数这些类,能够随意马虎疏忽。为了不便起见,我编写了一个小小的preloader类,以使疏忽文件更易,以下所示:

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支持

一个颇有出路的特点多是基于composer的主动预加载处理计划,它曾经被年夜少数古代PHP名目所应用。人们在致力正在composer.json中增加预加载设置装备摆设选项,它将为您天生预加载文件!今朝,此性能仍正在开发中,但您能够正在此处存眷。

#效劳器要求

正在应用预加载时,对于devops方面另有两件更首要的事件需求说起。

您曾经晓得,需求正在php.ini中指定一个条款能力进行预加载。这象征着假如您应用同享主机,您将无奈自在地设置装备摆设PHP。实际上,您需求一个公用的(虚构)效劳器,以便可以为单个名目优化预加载的文件。记住这一点。

还要记住,每一次需求从新加载内存文件时,都需求从新启动效劳器(假如应用php-fpm就足够了)。这对年夜少数人来讲仿佛是不言而喻的,但依然值患上一提。

#功能

如今到最首要的成绩:预加载真的能进步功能吗?

谜底是一定的:Ben Morel分享了一些基准测试,能够正在以前链接的相反的composer成绩中找到。

风趣的是,您能够决议仅预加载“hot classes”,它们是代码库中常常应用的类。Ben的基准测试显示,只加载约莫100个抢手类,实际上能够取得比预加载一切类更好的功能收益。这是功能晋升13%以及17%的区分。

当然,应该预加载哪些类取决于您的特定名目。理智的做法是正在开端时尽可能多地预加载。假如您的确需求大批的百分比增进,您将不能不正在运转时监督您的代码。

当然,一切这些工作均可以主动化,未来可能会完成。

如今,最首要的是要记住composer将增加支持,这样您就不用本人制造预加载文件,而且只需您齐全管制了此性能,就能够正在效劳器上轻松设置此性能。

翻译:https://stitcher.io/blog/preloading-in-php-74

以上就是PHP 7.4中的预加载(Opcache Preloading)的具体内容,更多请存眷资源魔其它相干文章!

标签: php7开发教程 php7开发资料 php7开发自学 Php 7.4

抱歉,评论功能暂时关闭!