具体PHP的内核和机制问题,应该是09年底到11年那阵子很火,很多人从不同角度各侧面都分享过很多次了,我就不重复造轮子了。12年刚进公司时做了次分享,如果有兴趣可以参考附件的ppt。 本文主要是从这里出发,谈论PHP的性能问题。
1. Where,PHP怎么工作的,性能瓶颈的背景
先上一张12年时画的关于PHP的流程图。
图1:PHP及其SAPI的工作流程
(1)看图说话
中间实现部分分开上下两部分,上面为上层应用与SAPI,下面为PHP。
右边虚线为PHP的前置启动,一般需要上层应用启动监听相应的web端口,同时PHP-CGI启动,初始化各扩展分配所需的资源,并向ZendEngine注册函数。
左上虚线为关闭时行为
左下为PHP核心与ZendEngine,ZE主要有3大类功能:
A. PHP语言编译:把PHP语言解释为opcode,然后在vm中执行
B. PHP语言功能:比如OO的实现,变常量等数据结构实现,内存管理,接口,闭包和异常管理
C. Zend API:对信号、模块和内置函数、类的管理,接口管理,包括注册、参数解析等
图2:Zend Engine功能
(2)上层应用和SAPI
首先PHP是基于请求的,通常实际生产环境中会有上层应用,并提供SAPI来为解耦与兼容不同的上层应用,比如Apache,IIS,CLI等。
上层应用在监听到动态请求,通常通过请求资源的扩展名.php判断,把请求处理转发给PHP-CGI处理。
这样做,可以很好的解耦,保证兼容上层应用。
但,这样也导致了各种问题。
2. What,PHP性能问题具体是?
其他观点大家肯定也听很多了,我给大家说几个典型的吧:
我记得之前问过鸟哥,答曰各种hashtable检索,比如变量表,常量表,类表等。这从语言本身的实现层面来说没有问题。
以我自己的经验来看,PHP性能瓶颈主要在以下几个方面:
(1)解释型语言的Opcode解析成本问题
一般都是都是用opcode cache。eacc之类的,这个方面产品多,应用广,较成熟不累述。
但是,在语言编译优化上,其实远远不够,比如鸟哥2013年11月分享的Opcache做的14点优化里,都与成熟的C/C++编译器gcc根本没 法比。 相对性能对比而言,eacc ~= opcache > APC。主要原因是eacc在锁机制上实现的更好,spinlocks 相对于 mutex 对多核CPU的利用更有优势。
测试数据 eacc 大约比 APC提升13%
http://2bits.com/articles/benchmarking-apc-vs-eaccelerator-using-drupal.html
而opcache对于APC的提升是7-10%。
但是opcode cache,仅解决了解释成本的问题。
(2)字节码跑在虚拟机上的问题
最终字节码必须跑在虚拟机上,而非直接执行,所以性能上终究有问题。简单的类比就是Java和C/C++的性能区别。
而Facebook的Hiphop(HHVM)把PHP语言转为C++然后再编译执行,它对性能改善近1.8倍,很好的说明了这个性能问题。
目前据我了解,贴吧的百度RD不少人是往这块努力的,LAMP架构和性能优化的项目。
另外,其实HHVM的方案仍然不够彻底,另外有个最理想的方案,正是我目前在努力的方向,具体等出结果再告知。
(3)SAPI的机制问题
这里涉及到respawn机制,就是面向请求的问题
PHP的生命周期是从WebServer启动时spawn一个cgi进程开始的,在主流的worker模式中,在响应若干请求后(MaxRequestsPerChild)会respawn一次。
而每次响应请求时,必须往ZE里初始化资源和注册模块的函数。Minit(M的次数与所调用的扩展个数相同)。
而请求结束后,处理结果是要从PHP传给上层应用,最后执行Mshutdown清理。
这样相比于C/C++的后端服务而言,做了相当多的额外无用功能,这是导致PHP性能低的关键。
mod_php和fastcgi目前仍然是这样方式,所以主流PHP的性能瓶颈在这点上,据我所知,百度和腾讯(朋友网除外)目前都是这种方式。
Rasmus在08年的时候引入yahoo的信号延迟处理,为使信号屏蔽机制可用于 SAPI,提高PHP性能。之后,在11年的时候提出一种方案,就是用zeroMQ + libevent作为扩展的方式,用PHP写调用这些扩展的接口去监听指定端口,这样就相当于用PHP进行后端服务处理。在我想来主要是为了解决SAPI 解耦成本的问题,尝试不通过webserver,直接用PHP本身去实现webserver并监听端口请求。
果然,PSF给PHP加上epoll/kqueue做了一套网络编程框架,TCP用Multi-Reactor,UDP借鉴LVS的半同步/半异步模型,性能能达到2-8wGPS的水平,自身用PHP实现了一套web server。
这样就既可以和node.js一样用全异步回调方式处理业务逻辑,也可以实现多进程同步实现自身闭环,而不用再依赖恶心的sapi respawn机制了。以下是PSF的性能测试数据:
(4)弱类型导致的内存开销问题
图3:ZVAL的数据建构
ZVAL的数据结构可以看到,为了对弱类型的支持,实际上很大程度增加变量基础结构的内存消耗。导致PHP的内存开销居高不下。具体内存占用实测如下:
做过一个简单的对比,一个包含1000个元素的二维数组,其脚本的文本文件大小 133,625字节,加载成PHP数组后,实际占用空间为577368字节,大概在4.27-4.3倍左右。 扣除掉回车,=> 逗号,单引号,大小为51444,倍数为11.22倍。结构类似以下(占用800字节, C语言中这种结构体, 大概占用实际空间29字节):
array(
760 => array (
'cid1' => 1,
'cid2' => 760,
'cid3' => 0,
'className' => '海外地区',
'level' => 2,
'deleted' => 0,
),
……
)
800 / 29 = 27.58倍,大约PHP数组对内存的开销比例是C语言结构体的20-30倍。
弱类型本身不是问题,但如果在PHP中要实现自定义数据类型是以zval为基础,所以只能通过扩展开发来实现对内存的精细控制。
(5)弱类型导致的CPU运算开销
由于逻辑处理始终要考虑多种数据类型,中间的运算与计算实现成本较强类型增加很多。如快排实现,单独写了个zend_qsort.c。
(6)Zend Engine的问题
核心本身的问题,也是PHP性能瓶颈的一个关键点,Zeev和Andi的Zend引擎,显然没有达到业界的顶级水平。 比如垃圾回收机制,Java是火车头算法,而PHP的CG没有类似的算法,内存分配是,以块为单位去分配的,但是在回收的时候,只是
(7)PHP圈子不具备成熟的优化生态
我在这个圈子有9年多了,看着很多PHP程序员从起步开始,要不然转架构,要不然转C/C++,转了后鲜少仍做原来的。
或者有很多程序员其实本身就是C语言开发,实际产品需要做的业务开发。
像之前Rasmus带的老yahoo的一些程序员,其实都算是C/C++程序员,那么成为PHP语言本身的开发者相对容易。但他们其实是C语言开发者,而PHP语言对他们而言则相当于是一款产品。
这样就很难有靠PHP语言入门的程序员,能够用成长后的产出对PHP生态圈产生大贡献。
主要原因也是因为PHP仍在前端或业务端打转,涉及到优化时,C/C++语言程序员会拥有更大更扎实的知识积累与逻辑熟悉度。
Tobe continued: 3. How,PHP性能如何优化?
引用:
惠新宸(2013) 一个关于Zend O+的小分享, 详见http://www.laruence.com/2013/11/11/2928.html
Rasmus Ledorf(2008) Zend Signal Handling, 详见:https://wiki.php.net/rfc/zendsignals
赵海平 (2012) The HipHop compiler for PHP, 详见:http://dl.acm.org/citation.cfm?id=2384658
https://github.com/facebook/hhvm/
韩天峰(2012) 朋友网PHP高性能Server框架PSF介绍, http://km.oa.com/articles/show/133541
Michiaki Tatsubori,Akihiko Tozawa,Toyotaro Suzumura,Scott Trent,Tamiya Onodera (2010) Evaluation of a just-in-time compiler retrofitted for PHP,
详见: http://dl.acm.org/citation.cfm?id=1736015