说起 Opcache
,PHP 的小伙伴们肯定都已经耳熟能详了,早在 2013 年 Zend 公司就已经开发出了一个名为 O+
的性能优化工具,从 PHP 5.5 开始,这款工具也跟随源码里一起发布了,并且更名为 Opcache
。
Opcache 性能优化的原理
首先要了解一下 Opcode
,这是一种 PHP 文件被 Zend 引擎编译后的中间语言,就像 Java 的 ByteCode
,或 .NET 的 MSL
,比如你写了如下代码并运行:
<?php
echo 'Hello World';
$a ` 1 + 1;
echo $a;
Zend 引擎在执行上面的代码时会经过如下 4 个步骤:
(1)Scanning,re2c 进行词法分析和语法分析,将 PHP 代码转换为语言片段(Tokens)。
(2)Parsing,将 Tokens 转换成简单而有意义的表达式。
(3)Compilation,将表达式编译成 Opcodes。
(4)Execution,顺次执行 Opcodes,每次一条,从而实现 PHP 脚本的功能。
如果你在 php.ini
中开启了 Opcache
,那么每次请求来临时,Zend 引擎就不需要重复执行前面 3 步,从而大幅提升运行的性能。
更多内核细节,可以阅读下面这篇文章:
配置优化
PHP 官网有 Opcache
所有配置参数的解释,可以去了解一下:
下文我将对几个 Opcache
常用配置参数进行讲解。
opcache.enable
此参数的值为 1
代表开启 Opcache
,值为 0
代表关闭 Opcache
,默认值为 1
。
opcache.memory_consumption
此参数的值代表 Opcache
占用内存的大小,单位是 MB
,默认值为 64
。建议根据服务器内存情况来设置,比如我的博客服务器的内存是 2 G
,我将此参数的值设置为 256
。
opcache.interned_strings_buffer
PHP 使用了一种叫做 字符串驻留
(string interning)的技术来改善性能。例如,如果你在代码中使用了 1000 次字符串 foobar
,Zend 引擎在第一次使用这个字符串时会分配一个不可变的内存区域来存储这个字符串,之后的 999 次都会直接引用这个内存区域,而不需要重复创建。
此参数将 字符串驻留
这个特性提升一个层次,默认情况下这个不可变的内存区域只会存在于单个 php-fpm
的进程中,如果设置了这个选项,那么这个内存区域将会在所有 php-fpm
进程中共享。在比较大的应用中,这可以非常有效地节约内存,提高应用的性能。
此参数的值的单位是 MB
,默认值为 8
,建议根据服务器内存大小,设置一个大于 64
的值即可。
opcache.max_accelerated_files
Zend 引擎在第一次执行某 PHP 文件后,会将该文件的 OPcode
存储在哈希表中,之后的请求直接从哈希表中找到相应文件的 OPcode
,从而达到性能优化,而此配置选项决定了可以存储的 PHP 文件数量上限。
Zend 引擎对此配置参数的真实取值是在质数集合 { 223, 463, 983, 1979, 3907, 7963, 16229, 32531, 65407, 130987 } 中找到的第一个大于等于参数值的质数,例如设置此参数的值为 222
,则真实取值为 223
。
那么如何知道我们应用中的 PHP 文件数量呢?进入应用目录,使用如下命令即可查看应用中的 PHP 文件数量:
cd /home/www
find . -type f -print | grep php | wc -l
经过测试,我发现我应用的 PHP 文件数量为 19196
,所以我将该参数的值设置为 32531
。
opcache.validate_timestamps
如果此参数的值设置为 1
,那么 Zend 引擎在收到请求时,会每隔一段时间检测一次被请求的 PHP 文件是否已更新。如果文件已更新,就会重新对该文件进行语法分析、编译等步骤,生成新的 Opcode
。
检测的周期是根据另一个参数 opcache.revalidate_freq
而定的,每次检测都是一次 stat
系统调用,众所周知,系统调用会消耗一些 CPU 时间,并且 stat
系统调用会进行磁盘 I/O
,更加浪费性能。
不仅如此,假设你对服务器中的 PHP 文件进行了一次大量的更新,更新的过程中部分旧的文件会因为未过期而依然生效,和部分已生效的新文件混合在一起产生作用,必然会产生不确定因素,带来很多麻烦,所以建议将此参数的值设置为 0
。
不过需要注意的是,设置为 0
后,无论你怎么更新文件,Zend 引擎都会使用已缓存的 Opcode
,除非重启 php-fpm
或使用 opcache_reset()
方法清空缓存。有人可能会觉得重启 php-fpm
很麻烦,万一重启过程中,正在执行的任务被中断了怎么办?其实解决办法很简单,写一个启动脚本,进行平滑重启即可。
根据提示将该脚本下载至你的服务器,即可运行以下命令进行平滑重启:
service php-fpm reload
opcache.revalidate_freq
当 opcache.validate_timestamps
设置为 0
时,此参数将被忽略。
设置为 1
时,此参数的值决定了检测 PHP 文件是否已更新的周期,单位为 秒
,默认值为 2
。Opcache
依据 PHP 文件的 Modify 时间戳
来判断文件是否已更新,使用 stat
命令可以查看 Modify 时间戳
。
opcache.file_update_protection
当 Zend 引擎执行某 PHP 文件时,如果该文件的 Modify 时间戳
距当前时间的差值小于此参数的值,则该文件不会被缓存,此参数值的单位为秒,默认为 2
。
此参数的目的是为了防止文件还未修改完成就被 Opcache
缓存了,从而产生错误。而实际生产环境中,我们将 opcache.validate_timestamps
设置为 0
,文件只要被访问一次,就会被永久缓存,除非重启 php-fpm
才会刷新缓存,所以此参数没什么用,还浪费性能,建议设置为 0
。
opcache.huge_code_pages
众所周知,Linux 系统默认内存是以 4KB 进行分页的,而虚拟地址和内存地址是需要转换的,转换过程需要进行查表,CPU 为了加速查表会内建 TLB(Translation Lookaside Buffer),而 TLB 的大小是有限的,分页越小,表里的条目也就越多,TLB 的 Cache Miss 也就越高。
所以我们如果启用大内存页,就能间接降低 TLB 的 Cache Miss,而 Opcache
也能使用 Hugepage
来缓存 Opcodes
,从而达到性能优化的目的。
此参数值为 1
即可开启以上功能,默认值为 0
。
需要注意的是此参数需要系统开启 Hugepage
功能,使用如下命令可以查看当前系统 Hugepage
的信息:
cat /proc/meminfo | grep Huge
运行该命令会输入类似如下结果:
[root@leo ~]# cat /proc/meminfo | grep Huge
AnonHugePages: 495616 kB
ShmemHugePages: 0 kB
HugePages_Total: 0
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
Hugetlb: 0 kB
可以看到 HugePages_Total
等参数的值为 0
,也就是未开启 HugePages
功能。
运行如下命令即可开启 HugePages
,其中 128
代表 HugePages
的大小,单位是 MB
:
sysctl vm.nr_hugepages`128
建议根据服务器内存情况进行分配 HugePages
,例如我的服务器是 2G
,设置为 128
,可以自行参考进行设置。
完整配置
下面的配置是我正在使用的 Opcache
配置,只有 6
个选项需要设置,其他选项如果不了解最好不要设置,使用默认值即可,避免发生意外情况。
修改配置的方法很简单,编辑 php.ini
,将所有 [opcache]
下的相关内容全部删除,替换为如下内容:
[opcache]
opcache.memory_consumption`512
opcache.interned_strings_buffer`64
opcache.max_accelerated_files`32531
opcache.validate_timestamps`0
opcache.file_update_protection`0
opcache.huge_code_pages`1
修改完毕后需要重启 php-fpm
使配置生效:
service php-fpm reload
1 条评论
555