当解释器完成对脚本代码的分析后,便将它们生成可以直接运行的中间代码,也称为操作码(Operate Code,opcode)。Opcode cache的目地是避免重复编译,减少CPU和内存开销。如果动态内容的性能瓶颈不在于CPU和内存,而在于I/O操作,比如数据库查询带来的磁盘I/O开销,那么opcode cache的性能提升是非常有限的。但是既然opcode cache能带来CPU和内存开销的降低,这总归是好事。
这得从PHP代码的生命周期说起,请求PHP脚本时,会经过五个步骤,如下图所示:
Zend引擎必须从文件系统读取文件、扫描其词典和表达式、解析文件、创建要执行的计算机代码(称为Opcode),最后执行Opcode。每一次请求PHP脚本都会执行一遍以上步骤,如果PHP源代码没有变化,那么Opcode也不会变化,显然没有必要每次都重行生成Opcode,结合在Web中无所不在的缓存机制,我们可以把Opcode缓存下来,以后直接访问缓存的Opcode岂不是更快,启用Opcode缓存之后的流程图如下所示:
Opcode是一种PHP脚本编译后的中间语言,就像Java的ByteCode,或者.NET的MSL,举个例子,比如你写下了如下的PHP代码:
< ?php echo "Hello World"; $a = 1 + 1; echo $a; ?>
PHP执行这段代码会经过如下4个步骤(确切的来说,应该是PHP的语言引擎Zend)
1)Scanning(Lexing) ,将PHP代码转换为语言片段(Tokens)。 2)Parsing, 将Tokens转换成简单而有意义的表达式。 3)Compilation, 将表达式编译成Opocdes。 4)Execution, 顺次执行Opcodes,每次一条,从而实现PHP脚本的功能。
Optimizer+(Optimizer+于2013年3月中旬改名为Opcache),OPcache通过将PHP脚本预编译的字节码存储到共享内存中来提升PHP的性能,存储预编译字节码的好处就是省去了每次加载和解析PHP脚本的开销。
PHP 5.5.0 及后续版本中已经绑定了 OPcache 扩展。 对于 PHP 5.2,5.3 和 5.4 版本可以使用 » PECL扩展中的OPcache库。
PHP 5.5.0及后续版本
OPcache只能编译为共享扩展。如果你使用–disable-all参数禁用了默认扩展的构建,那么必须使用–enable-opcache选项来开启OPcache。编译之后,就可以使用 zend_extension 指令来将 OPcache 扩展加载到 PHP 中。
推荐的php.ini设置
使用下列推荐设置来获得较好的性能:
opcache.memory_consumption=128 opcache.interned_strings_buffer=8 opcache.max_accelerated_files=4000 opcache.revalidate_freq=60 opcache.fast_shutdown=1 opcache.enable_cli=1 opcache.save_comments=0
你也可以禁用 opcache.save_comments 并且启用 opcache.enable_file_override。 需要提醒的是,在生产环境中使用上述配置之前,必须经过严格测试。 因为上述配置存在一个已知问题,它会引发一些框架和应用的异常, 尤其是在存在文档使用了备注注解的时候。
以下是opcache的配置说明,其中给有值得都是默认配置:
; opcache的开关,关闭时代码不再优化. opcache.enable=1 ; Determines if Zend OPCache is enabled for the CLI version of PHP opcache.enable_cli=1 ; OPcache的共享内存大小,以兆字节为单位。总共能够存储多少预编译的PHP代码(单位:MB) ; 推荐128 opcache.memory_consumption=64 ; 用来存储临时字符串的内存大小,以兆字节为单位. ; 推荐8 opcache.interned_strings_buffer=4 ; 最大缓存的文件数目200到100000之间. ; 推荐4000 opcache.max_accelerated_files=2000 ; 内存"浪费"达到此值对应的百分比,就会发起一个重启调度. opcache.max_wasted_percentage=5 ; 开启这条指令, Zend Optimizer + 会自动将当前工作目录的名字追加到脚本键上,以此消除同名文件间的键值命名冲突.关闭这条指令会提升性能,但是会对已存在的应用造成破坏. opcache.use_cwd=0 ; 开启文件时间戳验证 opcache.validate_timestamps=1 ; 检查脚本时间戳是否有更新的周期,以秒为单位。设置为0会导致针对每个请求,OPcache都会检查脚本更新. ; 推荐60 opcache.revalidate_freq=2 ; 允许或禁止在include_path中进行文件搜索的优化. opcache.revalidate_path=0 ; 如果禁用,脚本文件中的注释内容将不会被包含到操作码缓存文件,这样可以有效减小优化后的文件体积,禁用此配置指令可能会导致一些依赖注释或注解的应用或框架无法正常工作,比如:Doctrine,Zend Framework2等. ; 推荐0 opcache.save_comments=1 ; 如果禁用,则即使文件中包含注释,也不会加载这些注释内容。本选项可以和opcache.save_comments一起使用,以实现按需加载注释内容. opcache.load_comments=1 ; 打开快速关闭,打开这个在PHP Request Shutdown的时候会收内存的速度会提高. ; 推荐1 opcache.fast_shutdown=1 ; 允许覆盖文件存在(file_exists等)的优化特性. opcache.enable_file_override=0 ; 定义启动多少个优化过程. opcache.optimization_level=0xffffffff ; 启用此Hack可以暂时性的解决"can’t redeclare class"错误. opcache.inherited_hack=1 ; 启用此Hack可以暂时性的解决"can’t redeclare class"错误. ;opcache.dups_fix=0 ; 通过文件大小屏除大文件的缓存,默认情况下所有的文件都会被缓存. ;opcache.max_file_size=0 ; 每N次请求检查一次缓存校验.默认值0表示检查被禁用了,由于计算校验值有损性能,这个指令应当紧紧在开发调试的时候开启. ;opcache.consistency_checks=0 ; 从缓存不被访问后,等待多久后(单位为秒)调度重启. ;opcache.force_restart_timeout=180 ; 日志记录level,默认只有fatal error和error. ;opcache.error_log= ; 将错误信息写入到服务器(Apache等)日志 ;opcache.log_verbosity_level=1 ; 内存共享的首选后台.留空则是让系统选择. ;opcache.preferred_memory_model= ; 运行php脚本时保护共享内存防止意外的写入,只对debug时有用. ;opcache.protect_memory=0
最后说一下使用opcache加速php时应该注意的坑:
opcache依靠的是PHP文件的modify time作为文件被修改的检测条件,基于这个会引发两个问题。
第一个问题是做版本回滚时,由于版本回滚后的文件修改时间比现有opcache缓存的文件时间要往前一些,所以可能会导致opcache不会清除缓存,需要手动reload。
第二个问题是做版本发布时,一般都是sync方式,可能会出现文件发布一半时被opcache缓存,用户访问会报程序错误,这个主要是因为文件内容缓存了一半,但是文件的时间戳不会在改变,所以就算opcache检测时也不会去读取新的文件了,需要手动reload。
针对这两个问题,不光reload可以解决,同样调用opcache的接口也可以清除opcache缓存。
你可以使用opcache_reset()或者或者opcache_invalidate()函数来手动重置OPcache。
opcache_reset():该函数将重置整个字节码缓存,在调用opcache_reset()之后,所有的脚本将会重新载入并且在下次被点击的时候重新解析。
opcache_invalidate():该函数的作用是使得指定脚本的字节码缓存失效。 如果force没有设置或者传入的是FALSE,那么只有当脚本的修改时间 比对应字节码的时间更新,脚本的缓存才会失效。
但是不推荐使用,个人在生产环境中进行代码发布后调用opcache_reset()清空缓存(测试确实可以清空缓存),出现过奇葩问题(访问量大的应用),后来就果断放弃了,使用了reload的方式。