1.缘起
很多人对PHP有一种很直观的感觉。这种感觉认为。PHP是一种灵活的脚本语言。它的库类是很丰富的。使用起来比较简单。它比较安全。它非常适合WEB开发。但是它存在性能低下的问题。那么PHP的性能。
难道真的就像大家所感觉到的那个样子一样糟糕吗。这篇文章主要就是围绕着这样的一种话题来进行探讨的。我们要从多个方面进行深入的分析。这几个方面包括了源码的情况。应用场景的特点。基准性能的表现以及对比分析的结果。我们希望通过真实的数据来说话。
2.从原理分析PHP性能
咱们就从原理这个角度去分析PHP的性能情况, 具体的分析内容需要分成几个部分来进行, 这几个部分分别是内存管理、变量、函数以及运行机制。
2.1内存管理
它采取了一种类似Nginx所使用的内存管理手法, 与此同时, PHP系统内部同样是依赖于内存池来运行的。而且在这个机制里, 它还特意地引入了关于内存池生命周期的相关概念内容。
只要是涉及到PHP脚本执行过程中所产生的一切与内存有关的各类操作行为, 以及任何外部扩展程序对内存的使用情况, PHP全都进行了统一化的全面托管处理工作。
不过有一点需要特别说明的是, 针对体积庞大的大数据块内存和那些零碎分散的小块内存, 它的处理逻辑完全不同, 分别采用了两套不一样的具体实现技术方案来进行针对性的优化设计细节调整, 想要更深入地了解这部分技术原理的话, 建议大家去翻阅下面所列举出来的那几篇相关文档资料作为参考依据。
在PHP的内存分配以及回收这一整套生命周期里面, 它采用的是先进行一次初始化的时候去申请, 然后再加上一个动态扩容的操作, 接着再配合上以内存标识作为基础的回收机制来实现管理, 并且还会在每一次请求完全结束以后, 直接就把那个内存池整体重新进行一下mask处理。
2.2变量
大家都明白, PHP属于那种变量类型并不固定的编程语言, 正因如此, 在PHP的内部结构里, 全部的PHP变量都会被转换为名叫Zval的一种特定类型, 这种类型的具体定义内容如下:
图一PHP变量
在变量的这个方面, PHP做了大量的优化工作, 比如说写时复制这一种机制。这样能够保证内存使用上的优化, 并且减少内存拷贝的次数, 你可以去参考那个叫做/php-using--zval/的这个东西。在数组的这方面, PHP内部会采用某种高效的什么东西来实现。
2.3函数
其实, 在 PHP 的内部运行机制中, 所有的 PHP 函数据说是都会被转换成内部的一个函数指针。这就好比我们在讨论扩展里面的具体那些函数一样, 它们的底层表现就是这样子的。
- ZEND_FUNCTION ( my_function );//类似function my_function(){}
当你去把它做内部展开的这个过程, 之后就会得到一个函数。
- void zif_my_function ( INTERNAL_FUNCTION_PARAMETERS );
- void zif_my_function(
- int ht,
- zval * return_value,
- zval * this_ptr,
- int return_value_used,
- zend_executor_globals * executor_globals
- );
从这个角度来看的话, PHP的函数, 在里面的话, 它也是对应着一个叫做函数指针的东西。
2.4运行机制
当我们谈论起PHP的运行性能时, 不少人都会开口说出一句话, 这句话的内容就是“C语言以及C++语言是属于编译类型, JAVA属于半编译类型, 而PHP则属于解释类型”, 这番话背后的逻辑含义在于, PHP这种编程语言在处理具体代码的时候, 必须先进行一个动态的解析动作, 然后才能让这些代码真的在系统中去跑起来, 正是因为存在这样一道必须要经历的解析前置步骤, 所以从原理上来做分析和判断的话, 那么PHP的表现出的运行效率是必然会很差的。
的确, 从PHP脚本的运行输出这个方面来看, 确实存在一个动态解析然后运行代码的过程。具体来讲, 关于PHP脚本的运行机制是怎么样的, 如下图所示。
图二 PHP运行机制
PHP的运行阶段也分成了三个阶段。
所以说, 在PHP这个语言的内部, 它其实是存在着编译这么一个过程的, 于是呢就因此生出了许许多多的cache工具来, 比如像那个apc, 还有eacc这类东西, 再或者等等之类的, 这些个cache工具在生产环境里面基本上是被当作必须配置的标配来看待的, 靠着它们, 是能够做到让PHP这个脚本被编译过一次之后就可以进行多次反复运行的这样一个效果的, 从这一点上看, PHP就是和JAVA那种半编译的机制是显得非常类似的。
所以, 你仔细瞧瞧运行的机制就会发现, PHP的运行模式和JAVA是非常类似的。它们都是先产生中间码。然后才会去运行在那些不同的虚拟机上面。
2.该过程处于动态运行的状态之中。
基于我们对上述多个方面的分析,我们可以发现, PHP这个东西, 它在内存管理这个块里, 在变量这块, 在函数这块, 还在运行机制那块, 都进行了非常多的工作, 也就是说, 它下了很大的功夫, 那么从原理的层面去进行观察的话, PHP这个问题, 它是不应该存在性能上的缺陷的, 所以它的性能表现程度, 至少也应该和Java的水平是处于一个比较接近的位置的。
这个时候就不得不把由PHP这个动态语言的特性所带来的性能问题, 拿出来谈一谈了, 因为PHP是在运行过程中去动态地进行处理的, 所以所有的那个变量, 还有那个函数调用, 再有就是对象调用, 以及作用域的实现等等这些事情, 都是需要在执行阶段的时候才会被确定下来的。
这个原因从根本上决定了PHP性能方面存在某些很难被改变的特征。比如那些在C语言或者这种能够通过静态编译阶段来确定的变量和函数, 在PHP里面是需要在动态运行过程之中才能被确定的状况。这种情况也就决定了PHP中间码无法直接进行运行操作, 而必须要运行在Zend引擎之上这一事实。
当我们聊到PHP里的变量具体是怎么搞的, 这时候有一个东西不得不提。这东西可以说是PHP的灵魂之一, 然后在PHP的内部被广泛地用到, 比方说包括那个变量符号栈啊、函数符号栈啊等等, 这些全都是基于这个基础来构建和实现的。
我们拿PHP这种语言里头的变量作为一个例子, 来给大伙儿好好说明一下PHP它在动态运行的时候, 都体现出了一种什么样的特点, 就比如说咱们看到下面这段代码的情况。
- $var = “hello, blog.xiuwz.com”;
- ?>
这代码一执行, 就会产生一个结果, 这个结果具体体现为, 在一个被称为是变量符号栈的那个东西里面, 往里面增加了一个新的项目。
当需要用到那个变量的时候, 就跑去变量符合栈里面去查找情况。这意思就是说, 变量调用这一动作, 对应出了有一个进行查找hash这个环节的过程。
在函数调用的方面, 其处理方式也基本上呈现出类似的状况, 那就是需要维持一个用来存放函数符号的栈()。
其实就是说, 大家可以从 PHP 的运行机制里面, 看到一些关于动态运行的变量查找的特点, 毕竟 PHP 这边的代码, 它是经过了这样一套流程下来的, 具体的做法, 就是先解释一下这个代码, 然后再经过编译环节, 下图里就画着这一整套的流程内容。
图3 PHP运行实例
从上面的图片里面可以看到, 在写PHP代码的后面一步操作里, 系统就把类符号表以及函数符号表等这些表格给弄出来了。等到真的要去执行程序的时候, Zend这个引擎会拿那个op code当作线索, 往对应的符号表里去找, 顺便把这些东西处理一下。
从某种程度上来讲, 在面对这样问题的当儿, 想要去找到解决办法的话, 那是特别难的。这是因为PHP语言的动态特性所决定的一个结果。
不过, 在国内还有在国外, 确实也有挺多人在那里琢磨解决方案的。为什么他们要这么去做? 因为这么做的话, 就有可能把PHP从根本上进行彻底的优化。这里边比较典型的例子, 就有的()了。
2.6结论
从上面的分析这个情况来看, 在基础的内存管理、变量、函数、运行机制方面这件事儿上, PHP本身它是不会存在明显的性能差异的。
但是由于PHP这个动态运行特性, 它决定了PHP和其他的那些编译型语言相比一下, 所有的变量查找、函数运行等等这些操作, 它就会多出一些hash查找的CPU开销, 还有额外的内存开销。至于说这种开销它具体有多大这个问题的话, 这可以通过后续的基准性能和对比分析来得出结果。
因此, 我们大体上还是可以看出来, PHP在这样一些方面的场景下是比较不太适合的, 例如说那些涉及到大量计算性任务的场合, 或者是需要进行大数据量的运算的时候, 还有那些对于内存要求来说是很严格的应用场景。
如果我们如果要来实现这些东西功能的话, 那么我们也是建议要通过扩展的方式去实现这种操作, 然后再对外提供一些那种可以被PHP去调用的钩子函数。通过这种方式, 是可以将内部的变量、函数等一系列开销给降低下来的。
3.基准性能
大家普遍都缺少那种能够直接拿来说话的、关于PHP基准性能的标准数据。大多数的同学心里都是有个大概的印象, 或者存在一种感性的理解。有一部分人觉得当前的表现就已经是PHP能够达到的极限了。
除此之外呢, 大家也很难找到非常权威的数字来说明框架本身到底是怎么样的性能状态, 以及框架会给整体的性能带来一个什么样的具体的影响程度。
本章旨在为基准提供一个参考的性能指标, 利用数据呈现的方式给相关人员提供直观的观察。
具体的, 基准性能包含这几个方面。
1.裸的PHP的性能情况是怎么样的, 它完成了基本的功能这一点是确凿无疑的。
2.裸框架的性能。只做最简单的路由分发, 只走通核心功能。
3.我们来聊一下标准模块基准性能这个事。说白了, 它就是拿那个具备全套服务模块功能的东西去做个基准测试, 看看表现咋样。
3.1环境说明
测试环境:
Uname -a
Linux 2.6.9_5-7-0-0 #1 SMP Wed Aug 12 17:35:51 CST 2009 GNU/Linux
Red Hat Linux AS 4 ( 3)
8个英特尔至强处理器, 型号为E5520, 主频是2.27GHz。
软件相关:
Nginx:
这个nginx版本是nginx的0.8.54, 它是由gcc3.4.5编译成的。
Php5这一项, 采用的是php-fpm这种方式。
PHP 5.2.8 (cli) (built: Mar 6 2011 17:16:18)
(c) 1997-2008 The PHP Group
Zend v2.2.0, (c) 1998-2008 Zend
使用版本号v0.9.5.3, (c)2004-2006, 由。
:
PHP框架。
其他说明:
关于目标机器这一对象, 它被安排进行部署的方式是:
脚本。
负责进行测试操作的机器, 还有被测试的目标机器, 它们之间是分开部署的。
3.2裸PHP性能
这是用最简单的方式写出来的一个PHP脚本。
- require_once ‘./actions/indexAction.php’;
- $objAction = new indexAction();
- $objAction->init();
- $objAction->execute();
- ?>
- Acitons/indexAction.php里面的代码如下
- class indexAction
- {
- public function execute()
- {
- echo ‘hello, world!’;
- }
- }
- ?>
经过使用压力工具开展测试活动, 其结果呈现如下。
3.3裸PHP框架性能
为了可以和3.2那个部分的内容进行对比分析的情况之下, 基于这个框架实现了类似的功能。代码的具体实现如下所示。
- require_once ‘Bingo/Controller/Front.php’;
- $objFrontController = Bingo_Controller_Front::getInstance(array(
- ‘actionDir’ => ‘./actions’,
- ));
- $objFrontController->dispatch();
对于压力测试所呈现的结果, 目前来看是这样的:
从该测试结果能够看出来, 框架虽然存在着一定的消耗情况, 可是关于整体的性能来讲的话, 所造成的影响那是非常的小。
3.关于基准性能的说明, 在四个标准PHP模块中。
所谓的标准PHP模块这个东西, 是指一个PHP模块在功能方面, 必须要具体具备的那些基本功能的总称。
就是把路由进行分发操作。
它会自动地进行装载这个操作。
进行LOG初始化和日志打印, 所以的UI请求都一条标准的日志。
采用的代码自动生成工具产生标准的测试PHP模块, 这个模块的名字叫做test。
测试结果如下:
3.以上这些就是最终的结论。
根据测试数据得出的结果, PHP这一技术本身的性能表现还是过得去的。基准层面的性能完全有能力支撑起几千甚至上万的每秒查询率。
那么,关于为什么在大多数的PHP模块里面表现不够理想这个问题, 其实大家这个时候更应该去把系统的瓶颈点给找出来, 而不是简简单单地说一句PHP不行, 那么我们换C语言来弄。在接下来的章节里, 我们会通过一些具体的例子来做对比, 采用C语言来进行处理的话, 不一定非要有特别明显的优势存在着的。
咱们可以拿这个基准数据作为一个参照去进行分析, 然后从这个过程中就能够很直接地看到并且归纳出来一些具体存在的结论。
1.就PHP这个语言自身的性能表现来说, 它也是相当不错的。在只处理简单功能的情况下能够有不错的表现。即便是在极限场景下, 其处理能力也能达到上万并发的水平。
2.PHP这个框架本身, 它在性能方面所造成的影响, 其实是相当有限的。特别是在面对存在些许业务逻辑还有数据交互的场景之中的时候, 那种影响程度, 几乎是可以被直接忽略掉的。
3.一个标准的PHP模块, 它的基准性能表现是能够达到当CPU处于空闲状态时的处理能力的80%这样的水平。(这个描述其实挺绕的, 但意思就是在这个特定环境下, 性能指标符合要求)
4.对比分析
在很多时候, 大家发现PHP模块的性能不行, 就会说一句OK, 然后表示我们要采用C语言来重写, 在公司内部, 采用C和C++来编写业务逻辑模块的情况是处处都有的, 在前几年这种情况甚至几乎是全部都是采用C语言的,那时候大家所写出的代码确实是一个痛苦的过程, 这种痛苦体现在调试的难度非常大, 敏捷开发的理念更是不用谈了, 是完全无法实现的。