一、前言
一个 request 从请求之初到最终处理完成总是需要经历编译、执行两个阶段,请求结束后关于这个请求的所有数据就被擦除了,下次请求时需要经历同样的过程。
在这个过程中我们很容易发现一个问题:当多次请求同一个脚本时,需要重复经历编译的过程。只要脚本的内容没有发生变化,那么多次编译的结果实际上是相同的,这样就导致在重复的工作上花费了大量时间。 opcache 就是用来解决这个问题的,它把编译后的结果缓存下来,再次请求时不需要再重新编译,而是使用缓存的内容,直接进入执行阶段,从而大大提高了 PHP 的性能。
Opcache —— 一个用于缓存 opcodes 以提升 PHP 性能的 Zend 扩展,它的前身是 Zend Optimizer Plus (简称 O+ ),目前 Opcache 已经是 PHP 非常重要的一个组成部分,它对于 PHP 性能的提升有着非常显著的作用。
PHP 编译生成的 opcodes 指令与 Java 编译生成的字节码的角色是相同的,Java 将字节码保存到了 .class 中,而 Opcache 也可以把 opcodes 指令缓存到文件中,
从而实现跨生命周期使用,从这个角度看 PHP 与 Java 是非常相似的。
二、Opcache 工作原理
Opcache 最主要的功能是缓存 PHP 代码编译生成的 opcodes 指令,它的工作原理
如下:
(1) 开启 Opcache 之后,它将 PHP 的编译函数 zend_compile_file 替换为 persistent_compile_file(),接管 PHP 代码的编译过程,当新的请求到达时,将调用 persistent_compile_file() 进行编译。
(2) 此时 Opcache 首先检査是否有该文件的缓存,如果有则取出,然后进行一系列的验证,如果最终发现缓存可用则直接返回,进入执行流程,如果没有缓存或缓存已经失效,则重新调用系统默认的编译器进行编译,然后将编译后的结果缓存下来,供下次使用。整体的处理流程如图所示:
三、内部实现
1、初始化
Opcache是一个Zend 扩展,在 PHP 的 module_startup 阶段将调用 Zend 扩展的startup方法进行启动。
Opcache定义的startup 方法为 accel_accept(),此时 Opcache 将覆盖一些 ZendVM 的函数句柄,其中最重要的一个就是上面提到的 zend_compile_file()。
同时,该过程还会分配一个 zend_accel_shared_globals 结构,这个结构通过共享内存分,进程间共享,它保存着所有的 opcodes 缓存信息,以及一些统计数据,ZCSG() 宏操作的就是这个结构。
2、缓存的获取过程
编译一个脚本调用的是 zend_compile_file(),此时将由 Opcache 的 persistent_compile_file() 处理。
这里说下Opcache 的比较重要的两个配置:
(1)opcache.validate_timestamps:是否开启缓存有效期验证,默认值为 1,表示开启,开启之后每隔 opcache.revalidate_freg 秒检査一次文件是否更新了;如果不开启则不会检査,脚本文件修改了只能重启服务才能生效。 opcache.revalidate_freq 默认为2s。
(2)opcache.revalidate_path:验证文件路径,默认值为 0,表示关闭。默认情况下, opcodes 缓存并不是通过完整的文件路径名称进行索引的,而是通过一个根据文件名、当前所在目录、 include_path 生成的 key,因此当编译的文件实际已经不存在了但是缓存还在的时候,就会使用已经失效的缓存,如果开启这个选项,将通过完整的文件路径检索缓存,并且检查文件是否存在,而不再使用那个key。
3、缓存的生成
在 persistent_compile_file() 中我们简单介绍了缓存的查询过程,如果没有缓存或者缓存失效了,则需要重新编译并缓存结果,其中编译的过程由opcache_compile_file()完成,编译完成后调用cache_script_in_shared_memory() 进行缓存。