php7.4 preload(预加载)知识介绍

分享于:2020-11-10 16:38:48

重点先知道


Preload 简明翻译是预加载,是基于 opcache 的一层升级,也是 opcache 的一部分。Preload 的灵感来自为 Java HotSpot VM 设计的"类数据共享"技术。我们的php-fpm应用开启了opcache,再开启preload,性能会进一步提升。


提升的原理是什么?


1128628-20180504142714761-711951956.png


opcache可以【消除编译开销】,不用每次请求都编译一次opcode,把第一次编译好的opcode放入共享内存

但是,执行代码前仍有一部分开销:


1)将类和函数的某些部分(opcode)从 共享内存缓存 复制到 php-fpm进程内存

2)PHP 仍然需要检查源文件是否已被修改。(当然可以把配置参数opcache.validate_timestamps设置为0,减少这部分开销。再配合opcache_reset这个函数使用。)

3)由于每个 PHP 文件都完全独立于任何其他文件进行编译和缓存,因此在将文件存储在 opcode 缓存中时,我们无法解决存储在不同文件中的类之间的依赖关系,并且必须在每个请求的运行时需要重新链接类依赖项


preload是可以优化这部分开销的。


preload使用


在php-fpm服务启动时,会将一组特定的 PHP 文件加载到内存中,并使其内容"永久可用"到该服务器将处理的所有后续请求。


参见php.ini配置文件中有这两个参数

; Specifies a PHP script that is going to be compiled and executed at server
; start-up.
; http://php.net/opcache.preload
;opcache.preload=

; Preloading code as root is not allowed for security reasons. This directive
; facilitates to let the preloading to be run as another user.
; http://php.net/opcache.preload_user
;opcache.preload_user=


opcache.preload string

指定要在服务器启动时期进行编译和缓存的 PHP 脚本文件,就是一个可正常执行的PHP文件。


一般在这个文件通过 include 或者 opcache_compile_file() 函数 来预加载其他文件。 所有这些文件中包含的实体,包括函数、类等,在服务器启动的时候就被加载和缓存, 对于用户代码来讲是“开箱可用”的。


opcache.preload_user string

考虑到安全因素,禁止以 root 用户预加载代码。该指令方便以其他用户预加载。


看官方的解释,opcache.preload填写的是一个PHP文件,代码就是使用require_once或者opcache_compile_file等这些引入文件的函数,把需要预加载的文件,在php-fpm启动时,一块加载到内存中


2020-11-10_165329.png


opcache.preload_user就填写php-fpm启动用户(非root用户)就可以。


注意:如果开启了  opcache.enable_cli   ,preload在cli模式下也生效


什么样的文件适合被提前预加载?


class, funciton, trait, interface 这些定义文件能够进行预加载(在满足依赖的情况下)。

【经过测试php7.4常量可以提前定义,到了php8就不可以了】


2020-11-10_165329.png


像这种配置类的PHP文件预加载了也没有意义。因为【include config.php】返回的配置数组需要赋值到一个全局变量上,不然就是一次多余的操作。而在预加载的文件中定义的常量、全局变量在后续的请求中是获取不到的。


预加载只加载文件,不执行文件,因此动态生成的一切无法被预加载。


当然因为类可以预加载,故【类常量】可以在后续的请求中获取到。配置文件可以定义到类常量上


preload效果介绍


preload.php 预加载test文件

opcache_compile_file('./test.php');


./test.php 定义一个函数

function test() {    
   echo 'This is test func';
}


然后创建一个index.php文件


<?php

echo 'start<br>';
test();
echo '<br>stop';


在浏览器执行index.php后会输出

start
This is test func
stop


这要是没有开启preload,肯定会报这样的错误:

PHP Fatal error:  Uncaught Error: Call to undefined function test()


开启了preload,被预加载的函数和类,会给人什么感觉,就像是内置的函数、内置的类一样。当逻辑代码需要用到相关的函数、类、接口时,【不需要重新引入,建立依赖,直接就能用】,所以性能得到提升!


preload的不方便与注意事项


1)不支持热更新,代码修改不方便。


php-fpm启动时就会把预加载的类、函数定义代码加载到内存中,所以有修改的地方必须重启php-fpm才生效


这就要求被加载的文件应该是很少改动的。php-fpm 重启过程中会有少量时间消耗,这个时间会断开所有的链接,这个是需要评估的情况,可以考虑在深夜重启。


2)容易引起多个项目之间的冲突。


所有预加载的文件都可在内存中用于所有请求。假如一台服务器有A、B两个项目,A项目定义了a()函数,把a()函数的代码预加载。如果B项目也定义了a()函数,B项目必然会被影响,出现重复定义a()函数的错误。(如果PHP没有配置error_log参数,这个错误都不容易发现。这个错误发生在脚本执行之前,自定义的错误处理程序处理不了。)


预加载的是A类,同样B项目也定义了A类,因两个项目同名的类实现代码不一样,B项目同样会被影响。


一个 配置文件 只有一个preload 配置选项,对于多项目的情况是局限性,类似共享虚拟云主机的情况无法解决。


这个特性可能会因为不熟悉特性类名冲突导致整个项目报错挂掉


针对这样的问题,需要启动一个新的php-fpm避免冲突。


3)预加载的类文件,必须预先加载它们的依赖项 - 接口,特征和父类。


如果引入几个类文件,没有提前require_once他们的依赖文件,例如接口,trait 和父类,php-fpm启动时将会抛出警告。


2020-11-10_165329.png


最后的建议


preload性能效果怎么样?肯定是可以提升的,至于大不大取决于项目的复杂度,只有在依赖繁多时才会起到明显效果。本身preload坑也挺多的,php7.4以后,只开启opcache,性能提高也非常明显。


建议:


在实际应用中,应该对经常使用的类进行预加载,而不要全部加载。


以TP6框架来说,需要评估是否加载 verdor 中全部的文件,文件越多需要的内存越大,只加载最关键的部分是最好的选择,较少的内存资源占用并带来性能提升。至于怎么快捷地找到关键的一些类,可以借助get_included_files()函数。


扩展


以下是楠神个人的猜想,我没有做过具体的实验去验证,如果我这个猜想不成立的话,那感觉preload真是特别废的新特性。不能因为使用preload就去修改大量框架代码,为提升一点性能付出更多时间成本是不划算的,还容易造成项目出问题。


大部分框架通过include_once或require_once函数引入PHP函数库文件(如common.php),当common.php被预加载了,就不会再次被引入了。include_once、require_once会避免重复被加载。如果是使用include、require函数引入,那肯定会被重复引入,引起函数重定义。


文件加载


大部分框架使用spl_autoload_register函数自动加载类文件,这个函数的原理应该是先判断有没有定义类,没有才去引入。如果一个类文件被预加载了,当调用这个类时,基本不会再触发spl_autoload_register函数。


类的自动加载


有兴趣的朋友可使用strace工具测试下试试。