php7 为什么这么快?
全新的 zval 更节约的空间, 栈上分配内存
zend_string 存储字符串的 Hash 值, 数组查询的时候不需要进行 Hash 计算
在 HashTable 桶内直接存数据, 减少了内存的申请次数, 提升了 cache 命中率和内存访问速度
zend_parse_parameters 改为了宏实现, 性能提升 5%
增加 opcode 指令 call_user_function,is_init/string/array,strlen,defined 函数变成 opcode 指令, 速度更快
排序算法的改进
PHP7 架构
Zend 引擎: Zend 引擎为 PHP 提供了基础服务, 包括词法分析 语法分析 ,AST 抽象语法树编译 opcodes 执行, PHP 的变量设计, 内存管理, 进程管理.
PHP 层: 绑定了 SAPI 层并处理与它的通信, 它同时对 safe_mode 和 open_basedir 的检测提供一致的控制层, 将 fopen(),fread() 和 fwrite() 等用户空间的函数与文件和网络 I/O 联系起来.
SAPI: 包括了 cli fpm 等, 把接口对外接口都抽象出来, 只要遵守 SAPI 协议便可以实现一个 server.
拓展: zend 引擎提供了核心能力和接口规范, 在此基础上可以开发拓展
这里的拓展分为了两种, 通常在 PHP.INI 中, 通过 extension = 加载的扩展我们称为 PHP 扩展, 通过 zend_extension = 加载的扩展我们称为 Zend 扩展, 但从源码的角度来讲, PHP 扩展应该称为 "模块"(源码中以 module 命名), 而 Zend 扩展称为 "扩展"(源码中以 extension 命名). 两者最大的区别在于向引擎注册的钩子, 向用户层面提供一些 C 实现的 PHP 函数, 需要用到 zend_module_entry(即作为 PHP 扩展), 而需要 hook 到 Zend 引擎的话, 就得用到 zend_extension(即作为 Zend 扩展).
PHP7 执行流程
词法分析, 把源代码切割成多个字符串单元 (Token)
语法分析器把 Token 转换成 AST 抽象语法树
抽象语法树转换成 opcodes(opcode 指令集合)
虚拟机解释执行执行 opcodes(opcode 是一组指令标识, 对应 handler 处理函数)
执行实例
词法分析
- <?PHP
- echo "Hello world";
切割成了 4 部分
- <?PHP => #define T_OPEN_TAG 379
- echo => #define T_ECHO 328
空格 => #define T_WHITESPACE 382
"hello world" => #define T_CONSTANT_ENCAPSED_STRING 323
语法分析
单独存在的词块不能完整表达语义, 还需要语法分析器, 它会检查语法, 匹配 Token, 对 Token 进行关联, 组织串联后的产物就是 AST.AST 分为多种类型, 对应 PHP 语法, 比如赋值语句, 生成的抽象语法树节点是 ZEND_AST_ASSIGN, 赋值语句的左右会被作为 ZEND_AST_ASSIGN 类型节点的孩子 (AST 是 PHP7 才加入的, 解耦了编译器和解释器).
opcodes
opcode 是 PHP 执行过程中的中间代码, 生成后由虚拟机执行, 生成的 opcode 是类似下面的样子
- line op
- 1 ECHO
- 2 RETURN
源码中对应的 opcode 及 handler
- ZEND_ECHO // handler:ZEND_ECHO_SPEC_CONST_HANDLER 实现的功能是输出 "hello world"
- ZEND_RETURN // handler:ZEND_RETURN_SPEC_CONST_HANDLER
PHP 生命周期
CLI 生命周期
php_module_startup: 注册全局变量 GPC 等, 加载内部拓展和外部拓展.
php_request_startup: 重置垃圾回收器, 初始化执行器, 初始化扫描器, 设置超时时间等.
- php_execute_script
- => compile_file
- => open_file_for_scanning(读取 PHP 代码内容, 并使词法分析指针指向第一个位置)
- => zendparse(词法分析语法分析后生成 AST) => init_op_array(初始化 op_array)
- => zend_compile_top_stmt(把 AST 转为 op_array)
- => pass_two(设置 op_array 对应的 zend 虚拟机 handler)
=> 生成 op_array
=> zend_execute(zend 虚拟机中执行 op_array)
php_request_shutdown: 调用所有关闭函数, 调用所有析构函数, 输出缓冲区内容, 重置最大执行时间, 关闭输出层 (HTTP 头等), 释放所有 request 的全局变量
php_module_shutdown: 调用 module 对应的 flush 函数, 清理持久化的符号表, 销毁全局变量, 关闭所有拓展, 关闭内存管理, 关闭输出 output, 析构垃圾回收
FPM 模式的生命周期
FPM 跟 CLI 模式不同的是, FPM 是常驻内存的, 所以 php_module_startup 只在启动进程的时候做一次初始化, 对应的 php_module_shutdown 也只做一次.
进入循环, 调用 fcgi_accept_request(accept) 阻塞等待, 如果请求进来, 则进入 php_request_startup, 初始化请求, 同时加了锁来防止惊群效应
- fcgi.c
- ...
- FCGI_LOCK(req->listen_socket);
- req->fd = accept(listen_socket, (struct sockaddr *)&sa, &len);
- FCGI_UNLOCK(req->listen_socket);
引用
PHP7 的性能优化总结
PHP 扩展与 Zend 扩展区别
《PHP7 底层设计与源码实现》 陈雷等
来源: https://www.cnblogs.com/jaychan/p/11218047.html