PHP中的OPCode和OPCache
概述
PHP作为一门动态脚本语言,其在zend虚拟机执行过程为:读入脚本程序字符串,经由词法分析器将其转换为单词符号,接着语法分析器从中发现语法结构后生成抽象语法树,再经静态编译器生成opcode,最后经解释器模拟机器指令来执行每一条opcode。
确切地说,分成以下四个步骤:
1. Scanning(Lexing) ,将PHP代码转换为语言片段(Tokens)
2. Parsing, 将Tokens转换成简单而有意义的表达式
3. Compilation, 将表达式编译成Opocdes(字节码)
4. Execution, 顺次执行Opcodes,每次一条,从而实现PHP脚本的功能。
PHP正常的执行流程
一、OPCode
1、什么是opcode?
当一个 PHP 文件被解释执行的时候,首先是被编译成名为 opcode (CPU 专用的机器语言指令)的中间代码,然后才被底层的虚拟机执行。 如果PHP文件没有被修改过,opcode 始终是一样的。这就意味着编译步骤白白浪费了 CPU 的资源。
2、什么是opcode缓存?
此时 opcode 缓存就派上用场了。通过将 opcode 缓存在内存中,它能防止冗余的编译步骤,并且在下次调用执行时得到重用。一般执行过程是先检查文件的签名(signature)或者修改时间,以防文件有改动。
3、PHP中启用Opcode缓存之后的流程如下:
二、OPCache
1、什么是OpCache?
OPCache 是Zend官方出品的,开放自由的 opcode 缓存扩展。PHP 5.5.0 及后续版本中已经绑定了 OPcache 扩展。
1)OPCache的作用
OPCache 通过将 PHP 脚本预编译的字节码存储到共享内存中来提升 PHP 的性能, 存储预编译字节码的好处就是 省去了每次加载和解析 PHP 脚本的开销。
主要是为了减少重复编译,从而减少CPU和内存的开销。
2)opcache缓存的内容
- OPCode
-
Interned String(可以理解为php请求生命周期中不需要释放的String,包括:变量名、类名、方法名、字符串、注释等)
2、OPCache的工作原理
Opcache工作原理其实是使用了共享内存机制,将需要缓存的内容放入到共享内存中,供其他进程使用。
因为Opcache在创建缓存的过程中不会阻止其他进程读取,所以在使用Opcache时要注意两点,不然会大量消耗资源:
1)不要给Opcache设置过期时间
2)不要在流量高峰期发布代码
3、OPCache的安装开启
PHP5.5.0以后版本自带Opcache加速器,但默认情况下是没有启用的。通过修改php.ini文件,在适当位置添加,比如下面:
重启服务器,然后我们可以通过phpinfo进行查看:
注意:
如果需要将 » Xdebug 扩展和 OPcache 一起使用,必须在 Xdebug 扩展之前加载 OPcache 扩展。
3、OPCache的配置优化
1 //添加Opcache扩展 2 zend_extension=opcache.so 3 //开启Opcache 4 opcache.enable=1 5 //cli环境下启用Opcache 6 opcache.enable_cli=1 7 //浪费内存的上限,以百分比计,如果达到该上限,Opcache则会清空并重新生成缓存。默认5% 8 opcache.max_wasted_percentage=5 9 //配置共享内存存储大小,单位MB 10 opcache.memory_consumption=128 11 //用来存储临时字符串的内存大小,单位MB。这个配置就是上面说到的Opcache缓存的interned_string,它会在第一次使用到interned_string时缓存到共享内存中,供其他进程后续使用 12 opcache.interned_strings_buffer=8 13 //用于控制共享内存最多可以缓存多少个文件,该值最小范围是200,在php5.5.6版本之后,最大值是1000000,命中率不到100%的话,可以试着提高这个值 14 opcache.max_accelerated_files=4000 15 //设置缓存的过期时间,为0的话则每次都要检查,这里设置为60s检查一次文件更新 16 opcache.revalidate_freq=60 17 //如果启用,OPcache会在opcache.revalidate_freq设置的秒数去检测文件的时间戳(timestamp)检查脚本是否更新。如果这个选项被禁用(设置为0),opcache.revalidate_freq会被忽略,PHP文件永远不会被检查。这意味着如果你修改了你的代码,然后你把它更新到服务器上,再在浏览器上请求更新的代码对应的功能,你会看不到更新的效果 18 opcache.validate_timestamps=0 //所以像我上面说的,在大流量高并发场景下,该项不要启用,切记 19 //启用后,可以将依赖Zend引擎的内存管理模块一次释放全部请求变量的内存,而不是依次释放每一个已分配的内存块。 20 opcache.fast_shutdown=1 21 //启用文件缓存(设置缓存路径),这样Opcache就可以将Opcode缓存到文件中,实现跨php生命周期缓存 22 opcache.file_cache=/tmp
4、部分配置参数说明
1) opcache.revalidate_freq
这个配置选项用于设置缓存的过期时间,如果设置为0,每次请求时,都会检查文件是否修改,很耗资源,所以如果暂时不需要使用opcache,直接把opcache.enable设置为0即可。
2)opcache.validate_timestamps
配置为1时,会根据revalidate_freq设定的值检查更新代码;设置为0时,永不检查。当需要一次更新大量代码的时候,可以设置为0,上传全部完成后,再设置为1。避免上传代码造成系统的不稳定。
注意:
如果禁用此选项,每次部署代码无法即时获得更新。需要使用 opcache_reset() 或者 opcache_invalidate() 函数来手动重置 OPcache,也可以 通过重启 Web 服务器来使文件系统更改生效。
补充:
validate_timestamps 配置为 0 以后,php是否会自动生成缓存,取决于部署php的方式
部署 PHP 的时候有两种方式,一种是直接覆盖旧文件,另一种是使用 CI 发布会自动生成新的部署目录,并通过软连接的方式指定到 web 目录。
a. 直接覆盖旧文件的方式下,opcache 确实不会自动生成缓存,因为 opcache 通过文件的真实路径进行缓存,如果文件存在就不会再次缓存,也就导致了部署后线上并没有看到新的功能代码。
b. cli的方式下,opcache会主动生成缓存,因为上面说了,opcache 是通过文件的真实路径进行缓存的,这就导致了每次部署都会生成缓存字节码,那么就导致了旧的缓存没有被清理,那么迟早有一天会撑爆内存。
所以,在部署代码的时候如何清理 opcahce 生成的缓存就成为了关键所在。
解决方案
- 平滑重启 php-fpm
- 通过 opcache_reset () 函数
- 第三方库
3)opcache.fast_shutdown
如果启用,则会使用快速停止续发事件。 所谓快速停止续发事件是指依赖 Zend 引擎的内存管理模块 一次释放全部请求变量的内存,而不是依次释放每一个已分配的内存块。
说明:从 PHP 7.2.0 开始,此配置指令被移除。 快速停止的续发事件的处理已经集成到 PHP 中, 只要有可能,PHP 会自动处理这些续发事件。
4)opcache.save_comments 默认1 开启
如果禁用,脚本文件中的注释内容将不会被包含到操作码缓存文件, 这样可以有效减小优化后的文件体积。 禁用此配置指令可能会导致一些依赖注释或注解的应用或框架无法正常工作, 比如: Doctrine, Zend Framework 2 以及 PHPUnit。
如果项目中没有用到依赖注解等,可以将这个选项关闭,否者应开启。
三、总结
合理的使用OPcahce将会减少Web服务器的CPU使用率,但是会增加一定的内存使用率。如果使用得到,这将会大大提高或服务端和PHP的吞吐量。
参考链接:
https://segmentfault.com/a/1190000021718693