一 zval使用栈内存
在Zend引擎和扩展中,经常要创建一个PHP的变量,底层就是一个zval指针。之前的版本都是通过MAKE_STD_ZVAL动态的从堆上分配一个zval内存。而PHP7可以直接使用栈内存。PHP代码中创建的变量也进行了优化,PHP7直接在栈内存上预分配zval。这样节约了大量内存分配和内存管理的操作。
PHP5
zval *val; MAKE_STD_ZVAL(val);
PHP7
zval val;
二 zend_string存储hash值,array查询不再需要重复计算hash
PHP7为字符串单独创建了新类型叫做zend_string,除了char *指针和长度之外,增加了一个hash字段,用于保存字符串的hash值。PHP中array是核心数据结构,PHP程序中往往都有大量的$array[$key]操作,虽然hashtable查找的时间复杂度是O(1),但$key要转为hash值是要经过计算的。不仅仅是array操作,实际上PHP底层对于类属性、类方法、函数,访问时都要先通过hashtable查找到对应的指针,再执行对应的操作。PHP7之前Zend引擎会有大量的CPU时间用于计算hash值。
实际上PHP程序运行起来之后,大部分情况下$key的值都是不变的。PHP7干脆将这个hash值保存起来,下次直接使用,这样就节省了大量的hash计算操作,PHP的hashtable与C数
组的性能一致。
从实际项目进行callgrind性能分析,会发现alloc和hash 2项操作就占用了相当大比例的CPU时间。PHP7优化之后这2项操作占用的CPU时间降低了非常多。(注:zend_hash仍然占12%,因为整体CPU降低了,所以总的耗时降低了不少)
三 hashtable桶内直接存数据
PHP5的hashtable每个元素都是一个 Bucket *,而PHP7直接存Bucket,减少了内存申请次数,提升了Cache命中率和内存访问速度。
四 zend_parse_parameters改为宏实现
PHP的C扩展函数与PHP中的变量进行参数输入时,要使用zend_parse_parameters()函数,这个函数根据一个字符串参数找到对应PHP的zval指针,然后进行赋值。 这个函数实际上有一定的性能消耗。PHP7直接使用宏替换了zend_parse_parameters函数,C扩展中不再需要使用zend_parse_parameters进行逐个参数的查找,宏展开后自动会实现参数赋值。仅此一项就提升了5%的性能。
五 新增加4种OPCODE
很多PHP程序中会大量使用call_user_function, is_int/string/array, strlen , defined 函数。PHP5 都是以扩展函数的方式提供,PHP7中这4类函数改成ZendVM的OPCODE指令,执行更快。
六 其他更多优化
除了上面5个主要优化点之外,PHP7还有其他更多的细节性能优化。如基础类型int、float、bool等改为直接进行值拷贝,排序算法改进,PCRE with JIT,execute_data和opline使用全局寄存器等等。PHP7对性能的优化会继续进行下去。
PHP7-alpha相比PHP5.6性能提升了近3倍。下面是WordPress在PHP7上的表现:
PHP7的新特性
除了性能优化外,PHP7新增加了2项重要的新特性。
1. 变量类型
PHP7版本函数的参数和返回值增加了类型限定。为什么PHP要加入类型,实际上此项特性是为了PHP7.1版本的JIT特性做准备,增加类型后PHP JIT可以准确判断变量类型,生成最佳的机器指令。
function test(int $a, string $b, array $c) : int {
//code
}
2. 错误异常
PHP程序出错后过去Zend引擎会发生致命错误并终止程序运行,PHP7可以使用try/catch捕获错误。底层使用Exeception代替了Fatal Error。这个特性表示PHP语言正在向一个更加规范的方向发展。应用层与底层在错误抛出的方式全部统一为异常。
try {
non_exists_func();
} catch (EngineException $e) {
echo "Exception: {$e->getMessage()}\n";
}
3. 匿名类
$test = new class("Hello World") {
public function __construct($greeting) {
$this->greeting = $greeting;
}
};
PHP7与JIT
最初PHP7性能优化的方向并不是以上所讲的,而是JIT。JIT是just in time的缩写,表示运行时将指令转为二进制机器码。Java语言的JVM引擎底层就是使用JIT将Java字节码编译为二进制机器码执行。PHP7开发过程中有一个中间版本是基于JIT,后来开发组发现使用JIT后,对于实际项目并没有有太大的性能提升,所以PHP7最终放弃了JIT方案,PHP7.0-final版本不会携带JIT特性。
但如果是密集计算类程序就不同了,使用JIT将PHP OpCode编译为机器码,运算的性能会大幅提升。PHP官方开发组在2014年底重启了JIT的开发工作。