最近PHP官方终于发布了传说中的PHP7,虽然只是alpha版。PHP7号称是新一代的PHP,官方开发组对Znd引擎底层做了大量修改来优化PHP的性能。可以说PHP7这个版本的主题就是性能优化。
在过去PHP一直以开发效率快著称,而语言本身的性能较差(当然比Python,Ruby还是要快一些的)。普通的Wb网站都是IO密集型的程序,瓶颈在MySQL上,所以体现不出PHP的性能劣势。但在密集计算方面比C/C++、Java等静态编译语言差几十倍甚至上百倍。另外使用设计非常复杂的开发框架,如Symfony、Laravl等,程序性能也会明显下降。
现在随着PHP越来越流行,像Facbook、新浪微博这样超大型规模的网站都在使用PHP。PHP语言性能问题就越来越严重了。Facbook有几十万台服务器,如果现有的PHP程序可以提升一部分性能,将会节约大量的服务器资源。所以就有了HHVM、Hack。Hack为PHP增加了类型,HHVM是一个重新设计的PHP引擎,实际项目中使用HHVM可以提近70%的性能。实际项目70%性能提升这是一个什么概念?腾讯QQ农场最初使用PHP开发,后因为性能问题使用C语言重构,完成后性能提升了%。
PHP官方也注意到了这个问题,所以就有了PHP7的开发计划。最新公布的PHP7-alpha在WordPrss项目中测试的表现已经超越了HHVM。未来PHP将会同时具备极高的开发效率和极高的性能,再结合Swool做异步编程,PHP势必会更加流行。
本文简单介绍一下PHP7做了哪些优化,可以提升如此多性能。
一zval使用栈内存
在Znd引擎和扩展中,经常要创建一个PHP的变量,底层就是一个zval指针。之前的版本都是通过MAKE_STD_ZVAL动态的从堆上分配一个zval内存。而PHP7可以直接使用栈内存。PHP代码中创建的变量也进行了优化,PHP7直接在栈内存上预分配zval。这样节约了大量内存分配和内存管理的操作。
PHP5
zval*val;MAKE_STD_ZVAL(val);
PHP7
zvalval;
二znd_string存储hash值,array查询不再需要重复计算hash
PHP7为字符串单独创建了新类型叫做znd_string,除了char*指针和长度之外,增加了一个hash字段,用于保存字符串的hash值。PHP中array是核心数据结构,PHP程序中往往都有大量的$array[$ky]操作,虽然hashtabl查找的时间复杂度是O(1),但$ky要转为hash值是要经过计算的。不仅仅是array操作,实际上PHP底层对于类属性、类方法、函数,访问时都要先通过hashtabl查找到对应的指针,再执行对应的操作。PHP7之前Znd引擎会有大量的CPU时间用于计算hash值。
实际上PHP程序运行起来之后,大部分情况下$ky的值都是不变的。PHP7干脆将这个hash值保存起来,下次直接使用,这样就节省了大量的hash计算操作,PHP的hashtabl与C数组的性能一致。
从实际项目进行callgrind性能分析,会发现alloc和hash2项操作就占用了相当大比例的CPU时间。PHP7优化之后这2项操作占用的CPU时间降低了非常多。(注:znd_hash仍然占12%,因为整体CPU降低了,所以总的耗时降低了不少)
三hashtabl桶内直接存数据
PHP5的hashtabl每个元素都是一个Buckt*,而PHP7直接存Buckt,减少了内存申请次数,提升了Cach命中率和内存访问速度。
四znd_pars_paramtrs改为宏实现
PHP的C扩展函数与PHP中的变量进行参数输入时,要使用znd_pars_paramtrs()函数,这个函数根据一个字符串参数找到对应PHP的zval指针,然后进行赋值。这个函数实际上有一定的性能消耗。PHP7直接使用宏替换了znd_pars_paramtrs函数,C扩展中不再需要使用znd_pars_paramtrs进行逐个参数的查找,宏展开后自动会实现参数赋值。仅此一项就提升了5%的性能。
五新增加4种OPCODE
很多PHP程序中会大量使用call_usr_function,is_int/string/array,strln,dfind函数。PHP5都是以扩展函数的方式提供,PHP7中这4类函数改成ZndVM的OPCODE指令,执行更快。
六其他更多优化
除了上面5个主要优化点之外,PHP7还有其他更多的细节性能优化。如基础类型int、float、bool等改为直接进行值拷贝,排序算法改进,PCREwithJIT,xcut_data和oplin使用全局寄存器等等。PHP7对性能的优化会继续进行下去。
PHP7-alpha相比PHP5.6性能提升了近3倍。下面是WordPrss在PHP7上的表现:
PHP7的新特性
除了性能优化外,PHP7新增加了2项重要的新特性。
1.变量类型
PHP7版本函数的参数和返回值增加了类型限定。为什么PHP要加入类型,实际上此项特性是为了PHP7.1版本的JIT特性做准备,增加类型后PHPJIT可以准确判断变量类型,生成最佳的机器指令。
functiontst(int$a,string$b,array$c):int{
//cod
}
2.错误异常
PHP程序出错后过去Znd引擎会发生致命错误并终止程序运行,PHP7可以使用try/catch捕获错误。底层使用Excption代替了FatalError。这个特性表示PHP语言正在向一个更加规范的方向发展。应用层与底层在错误抛出的方式全部统一为异常。
try{
non_xists_func();
}catch(EnginExcption$){
choExcption:{$-gtMssag()}\n;
}
3.匿名类
$tst=nwclass(HlloWorld){
publicfunction__construct($grting){
$this-grting=$grting;
}
};
PHP7与JIT
最初PHP7性能优化的方向并不是以上所讲的,而是JIT。JIT是justintim的缩写,表示运行时将指令转为二进制机器码。Java语言的JVM引擎底层就是使用JIT将Java字节码编译为二进制机器码执行。PHP7开发过程中有一个中间版本是基于JIT,后来开发组发现使用JIT后,对于实际项目并没有有太大的性能提升,所以PHP7最终放弃了JIT方案,PHP7.0-final版本不会携带JIT特性。
但如果是密集计算类程序就不同了,使用JIT将PHPOpCod编译为机器码,运算的性能会大幅提升。PHP官方开发组在年底重启了JIT的开发工作。
PHP的异步网络通信扩展Swool
PHP在大部分程序员印象中都是用来做Wb网站的。PHP没有像Python的Twistd、Tornado,Java的Ntty、Mina,JavaScript的Nod.js等框架,无法实现异步网络通信程序。PHP的Swool扩展就是为了弥补此项缺陷而诞生的开源项目。Swool是一个标准的PHP扩展,为PHP提供了一系列异步IO、事件驱动、并行数据结构功能。
Swool与Nod.js非常相似,不同之处是Swool在并行提供了底层支持。Nod.js是一个单进程单线程的程序,在多核服务器上无法发挥全部CPU核的计算能力。需要程序员自行使用child_procss/clustr扩展或者启动多实例,使程序能够利用到多核优势。而Swool在底层就支持了多线程/多进程,程序启动后就会创建好多个IO线程和多个Workr进程。程序员仅需配置线程/进程数量即可。
使用Swool开发的TCP服务器程序:
$srv=nwswool_srvr(.0.0.1,);
$srv-on(connct,function($srv,$fd){
choClint:Connct.\n;
});
$srv-on(rciv,function($srv,$fd,$from_id,$data){
$srv-snd($fd,$data);
});
$srv-on(clos,function($srv,$fd){
choClint:Clos.\n;
});
$srv-start();
Swool同样也内置了