共计 6813 个字符,预计需要花费 18 分钟才能阅读完成。
1 缘起
对于 PHP,很多人的直观感觉是 PHP 是一种灵便的脚本语言,库类丰盛,应用简略,平安,非常适合 WEB 开发,但性能低下。PHP 的性能是否真的就如同大家的感觉一样的差呢?本文就是围绕这么一个话题来进行探讨的。从源码、利用场景、基准性能、比照剖析等几个方面深入分析 PHP 之性能问题,通过实在的性能数据来谈话,最终找出影响 PHP 模块性能的关键因素。
2 从原理剖析 PHP 性能
从原理剖析 PHP 的性能,次要从以下几个方面:内存治理、变量、函数、运行机制、网络模型来进行剖析。
2.1 内存治理
相似 Nginx 的内存治理形式,PHP 在外部也是基于内存池,并且引入内存池的生命周期概念。在内存池方面,PHP 对 PHP 脚本和扩大的所有内存相干操作都进行了托管。对大内存和小内存的治理采纳了不同的实现形式和优化。在内存调配和回收的生命周期内,PHP 采纳一次初始化申请 + 动静扩容 + 内存标识回收机制,并且在每次申请完结后间接对内存池进行从新 mask。
2.2 变量
总所周知,PHP 是一种弱变量类型的语言,所以在 PHP 外部,所有的 PHP 变量都对应成一种类型 Zval,其中具体定义如下:
在变量方面,PHP 做了大量的优化工作,比如说 Reference counting 和 copy on writer 机制。这样可能保障内存应用上的优化,并且缩小内存拷贝次数(请参考 http://blog.xiuwz.com/2011/11…)。在数组方面,PHP 外部采纳高效的 hashtable 来实现。
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 的运行阶段也分成三个阶段:
●Parse。语法分析阶段。
● Compile。编译产出 opcode 两头码。
● Execute。运行,动静运行进行输入。
通过上图也能够看出,其实在 PHP 外部自身也是存在编译的过程。事实上,在规范的生产环境中,也都基本上利用了这个特点,比如说 opcode cache 工具 apc、eacc、xcache 等等。基于 opcode cache,能到做到“PHP脚本编译一次,屡次运行”的成果。从这点上,PHP 就和 JAVA 的半编译机制十分相似。
所以,从运行机制上来看,PHP 的运行模式和 JAVA 是十分相似的,都是先产生两头码,而后运行在不同虚拟机上。
2.5 动静运行
从下面的几个剖析来看,PHP 在内存治理、变量、函数、运行机制等几个方面都做了大量的工作,所以从原理来看,PHP不应该存在性能问题,性能至多也应该和 JAVA 比拟靠近。
但为什么还有很多人感觉 PHP 慢呢?尤其是一些计算量的性能比照上,总发现 PHP 解决的性能绝对比拟低效。这个时候就不得不谈 PHP 动静语言的个性所带来的性能问题了,因为 PHP 是动静运行时,所以所有的变量、函数、对象调用、作用域实现等等都是在执行阶段中才确定的。这个从根本上决定了 PHP 性能中很难扭转的一些货色:在 C/C++ 等可能在动态编译阶段确定的变量、函数,在 PHP 中须要在动静运行中确定,也就决定了 PHP 两头码不能间接运行而须要运行在 Zend Engine 上。
说到 PHP 变量的具体实现,又不得不说一个货色了:hashtable。Hashtable 能够说在 PHP 灵魂之一,在 PHP 外部宽泛用到,蕴含变量符号栈、函数符号栈等等都是基于 hashtable 的。
以 PHP 变量为例来阐明下 PHP 的动静运行特点,比如说代码:
- <?php
- $var =“hello, blog.xiuwz.com”;
- ?>
该代码的执行后果就是在变量符号栈(是一个 hashtable)中新增一个项
当要应用到该变量时候,就去变量合乎栈中去查找(也就是变量调用对出了一个 hash 查找的过程)。
同样对于函数调用也基本上相似有一个函数符号栈(hashtable)。
其实对于动静运行的变量查找特点,在 PHP 的运行机制中也能看出一些。
能够看出,PHP 代码在 compile 之后,产出的了类符号表、函数符号表、和 OPCODE。在真正执行的时候,zend Engine 会依据 op code 去对应的符号表中进行查找,解决。
从某种程度上,在这种问题的上,很难找到解决方案。因为这是因为 PHP 语言的动静个性所决定的。然而在国内外也有不少的人在寻找解决方案。因为通过这样,可能从根本上齐全的优化 PHP。典型的列子有 facebook 的 hiphop。
但所有的这种编译优化计划,都基本上是就义了 PHP 动静运行的个性。当然能够在具体的编译优化中去对动静个性做一些折中,但很难做到完完全全的兼容。
2.6 网络模型
目前采纳 PHP 的形式,比拟现实和通用的模式是采纳 fastcgi(PHP-FPM)。Php-fpm 在网络模型上比拟相似 nginx,采纳了多过程 Master+ 多 worker 的模式。Php-fpm 自身是基于 libevent 中的 epoll 模型。从网络模型来看,该形式也不会和其余网络模型存在性能差别。
2.7 论断
从下面剖析来看,在根底的内存治理、变量、函数、运行机制、网络模型方面,PHP 自身并不会存在显著的性能差别,但因为 PHP 的动静运行个性,决定了 PHP 和其余的编译型语言相比,所有的变量查找、函数运行等等都会多一些 hash 查找的 CPU 开销和额定的内存开销,至于这种开销具体有多大,能够通过后续的基准性能和比照剖析得出。
因而,也能够大体看出 PHP 不太适宜的一些场景:大量计算性工作、大数据量的运算、内存要求很严格的利用场景。如果要实现这些性能,也倡议通过扩大的形式实现,而后再提供钩子函数给 PHP 调用。这样能够减低外部计算的变量、函数等系列开销。
3 基准性能
对于 PHP 基准性能,目前短少规范的数据。大多数同学都存在理性的意识,有人认为 800QPS 就是 PHP 的极限了。此外,对于框架的性能和框架对性能的影响很没有响应的权威数字。
本章节的目标是给出一个基准的参考性能指标,通过数据给大家一个直观的理解。
具体的基准性能有以下几个方面:
1、裸 PHP 性能。实现根本的性能。
2、裸框架的性能。只做最简略的路由散发,只走通外围性能。
3、规范模块的基准性能。所谓规范模块的基准性能,是指一个具备残缺服务模块性能的基准性能。
3.1 环境阐明
测试环境:
Uname -a
Linux db-forum-test17.db01.baidu.com 2.6.9_5-7-0-0 #1 SMP Wed Aug 12 17:35:51 CST 2009 x86_64 x86_64 x86_64 GNU/Linux
Red Hat Enterprise Linux AS release 4 (Nahant Update 3)
8 Intel(R) Xeon(R) CPU E5520 @ 2.27GHz
软件相干:
Nginx:
nginx version: nginx/0.8.54 built by gcc 3.4.5 20051201 (Red Hat 3.4.5-2)
Php5:(采纳 php-fpm)
PHP 5.2.8 (cli) (built: Mar 6 2011 17:16:18)
Copyright (c) 1997-2008 The PHP Group
Zend Engine v2.2.0, Copyright (c) 1998-2008 Zend Technologies
with eAccelerator v0.9.5.3, Copyright (c) 2004-2006 eAccelerator, by eAccelerator
bingo2:
PHP 框架。
其余阐明:
指标机器的部署形式:
脚本。
测试压力机器和指标机器独立部署。
3.2 裸 PHP 性能
最简略的 PHP 脚本。
- <?php
- require_once‘./actions/indexAction.php’;
- $objAction = new indexAction();
- $objAction->init();
- $objAction->execute();
- ?>
Acitons/indexAction.php 外面的代码如下
- <?php
- class indexAction
- {
- public function execute()
- {
- echo‘hello, world!’;
- }
- }
- ?>
3.3 裸 PHP 框架性能
为了和 3.2 的比照,基于 bingo2 框架实现了相似的性能。代码如下
- <?php
- require_once‘Bingo/Controller/Front.php’;
- $objFrontController = Bingo_Controller_Front::getInstance(array(
- ‘actionDir’=>‘./actions’,
- ));
- $objFrontController->dispatch();
- ?>
从该测试后果能够看出:框架尽管有肯定的耗费,但对整体的性能来说影响是十分小的。
3.4 规范 PHP 模块的基准性能
所谓规范 PHP 模块,是指一个 PHP 模块所必须要具体的基本功能:
●路由散发。
●主动加载。
●LOG 初始化 &Notice 日志打印。所以的 UI 申请都一条规范的日志。
●错误处理。
●工夫校对。
●主动计算每个阶段耗时开销。
●编码辨认 & 编码转化。
●规范配置文件的解析和调用
采纳 bingo2 的代码主动生成工具产生规范的测试 PHP 模块:test。
3.5 论断
从测试数据的论断来看,PHP 自身的性能还是能够的。基准性能齐全可能达到几千甚至上 W 的 QPS。至于为什么在大多数的 PHP 模块中体现不佳,其实这个时候更应该去找出站长博客零碎的瓶颈点,而不是简略的说 OK,PHP 不行,那咱们换 C 来搞吧。(下一个章节,会通过一些例子来比照,采纳 C 来解决不见得有特地的劣势)
通过基准数据,能够得出以下几个具体的论断:
1、PHP 自身性能也很不错。简略性能下可能达到 5000QPS(50CPU IDLE),极限也能过 W。
2、PHP 框架自身对性能影响十分无限。尤其是在有肯定业务逻辑和数据交互的状况下,简直能够疏忽。
3、一个规范的 PHP 模块,基准性能可能达到 2000QPS(80 cpu idle)。
4PHP 与 C 性能比照剖析
很多时候,大家发现 PHP 模块性能不行的时候,就来一句“ok,咱们采纳 C 重写吧”。在公司内,采纳 C /C++ 来写业务逻辑模块的景象到处都有,在前几年甚至简直全部都是采纳 C 来写。那时候大家写的真是一个苦楚:调试难、麻利不要谈。
那么,本章节要议论的一个话题就是:C 写的业务逻辑和 PHP 写的业务逻辑模块进行性能比照,采纳实在的数据来谈话。
4.1 前提
为什么要特地说出这个前提呢?因为在现实状况下,一个性能采纳 PHP 实现,该性能铁定不可能比现实的 C 写进去好。这个前提须要特地留神。
但为什么还要比照呢?因为在现实情况下,能写出十分优良的 C 程序,并且在频繁批改的状况下还能做到齐全高性能的又有几个呢?并且在事实的利用中 C 实现的性能是否真的全都都比 PHP 要好好几倍呢?这些目前都没有确切的数据来论证。
所以,本章节的比照是基于事实中的状况来进行的,并采纳实在数据来谈话。
4.2 实在业务模块 PHP 模块 VS C 模块
4.2.1 业务模块介绍
一个实在的案列,该业务模块的流量高达数十亿。
该业务模块性能非常简单,下层是 web server,上游是各个数据模块。都是基于 socket 进行数据交互。该业务模块的次要工作模型是:响应 web server 的申请,依据申请从各个后端数据模块读取相应数据,并依据数据产出最终的 HTML 页面返回给 web 服务器。
为了不便后续介绍,定义 CUI 示意用 C 实现的模块,PHPUI 示意用 PHP 实现的模块。
4.2.2C/C++ 模块的性能数据后果
09 年,该模块重构抉择了一个新的 C /C++ 框架。过后重构的时候,该模块连贯的后端数据模块规模 在5-7个。
基于 C /C++ 的模块,最终测试数据数据分成两个局部:
一、性能比照测试。
基于过后线上压力,进行实在数据的性能测试。所以过后只测试一个压力数据如下:
压力:210QPS
CPU(IDLE):84.18
二、极限性能测试 1。
该测试模型是:CUI 只连贯一个外围数据模块,其余数据模块齐全敞开。
三、极限性能测试 2。
该测试模型是:CUI 连贯后端一个外围数据模块,3 个数据模块,其余数据模块不连贯。
4.2.3 PHP 实现模块的性能测试数据
到 11 年,基于 09 年的 CUI 基本上达到了代码不看保护的境地。而且这个时候,CUI 的极限性能曾经不到600QPS(次要起因是随着我的项目的倒退,后端数据模块的数目减少到 14 个)。据此,决定采纳 PHP 计划来重写整个模块,并产出最终的 pbui 模块。
性能测试后果分成两种:
1、PHPUI 连贯一个外围模块。
2、PHPUI 连贯后端所有模块(14 个)。
4.2.4 数据比照论断
因为 PHPUI 和 CUI 的业务逻辑和测试方法都不完全相同,所以抽取了局部大体能比照的点进行整顿。具体比照数据如下:
从下面的比照数据来看,在实在的业务我的项目中,PHPUI的性能并不会比 CUI 差。这个不是简简单单一个模块来验证的,在部门外面,咱们有不少模块都是从 C /C++ 迁徙到 PHP,从迁徙的后果来看,并没有存在质的性能降落,大部分模块迁徙后性能指标都是十分靠近的。
这个时候就须要思考为什么会这样了?细分来说有两个问题:
1、为什么在实在业务我的项目中,PHPUI 的性能并不会比 CUI 差太多?
2、为什么基准的 PHP 性能这么高,80CPU 的状况下 2000QPS,但到了实在的 PHP 模块中只能是 200QPS?
其实这两个问题,也能够归纳成一种起因:在实在业务我的项目中,影响性能更多的不是说采纳了什么语言,而是其业务相干的局部,比如说 socket 交互次数,比如说字符串解决,也比如说网络交互包大小。
OK。那么接下来的要害是找出影响性能的关键因素。
4.2.5 影响 PHP 模块性能的关键因素
从后面剖析,咱们得出,影响前端 PHP 模块性能的关键因素不是语言自身(是否是 PHP/JAVA/ C 都不重要)。那么到底影响 PHP 业务模块性能的关键因素在哪里呢?CPU 耗时是统计一个我的项目性能的关键点之一,思考到零碎中都打印出了系列日志。通过剖析日志中申请的耗时散布能够大体上看出关键点。
在咱们零碎中,CPU 耗时重点打印出以下几个方面:
1、申请总工夫。
2、申请要害函数的性能,其中所有的 socket 交互都有耗时计算。
3、模版渲染也是坏事的一个关键点。
在后面剖析中,咱们基本上断定 socket 和字符串解决是一个关键点之一,通过数据咱们来验证下。抽取一个模块指定数目的日志,进行综合剖析得出以下数据:
通过这个能够看出,在一个业务模块中,影响最大的是 socket 数据交互,其次是大量的字符串解决。具体细分来说是以下几个因素:socket 交互次数、socket 交互包大小、socket 交互响应工夫、字符串解决。
4.2.6 论断
通过上述剖析,能够得出以下论断:在前端业务模块中,PHP 语言自身不会成为性能瓶颈。因为影响性能的几个要害因数是:
● 网络交互数目。
● 网络交互数据大小,蕴含数据打包解包开销。
● 网络交互响应工夫。
● 大量的字符串解决。
5 最终论断
通过上述三个章节的具体分析,能够得出以下论断:
1、从 PHP 实现原理来看,PHP 属于半编译型语言,并且在各个方面都进行了大量的优化工作,自身不会存在显著的性能问题。但因为动静语言的个性,决定了 PHP 须要运行在 Zend Engine 虚拟机上,并且在变量查找、函数调用、作用域切换等各个方面须要一些额定开销。
2、从 PHP 的基准性能来看,PHP 自身不会存在显著的资源耗费,单机 QPS 可能轻松过 W,PHP 框架自身也不会对业务零碎的性能带来关键性的影响。
3、从实在的利用场景来看,基于 C 语言实现的模块不见得比基于 PHP 实现的模块性能高效很多。因为在实在的利用场景中,更多的性能开销在于网络数据交互和字符串解决。语言方面渺小的性能差别不会成为瓶颈。
据此,能够推出:基于 C 语言实现的大部分业务零碎都能够思考迁徙到 PHP 上来,一方面可能疾速开发,另外一方面性能也不会存在问题。