【编者按】此前,阅读过了不少关于 PHP 性能分析的文章,不过写的都是一条一条的规则,并且,这些规则并无上下文,也没有明确的实验来体现出这些规则的优点,同时讨论的也侧重于一些语法要点。本文就改变 PHP 性能分析的角度,并经过实例来分析出 PHP 的性能方面须要注意和改进的点。php
对 PHP 性能的分析,咱们从两个层面着手,把这篇文章也分红了两个部分,一个是宏观层面,所谓宏观层面,就是 PHP 语言自己和环境层面,一个是应用层面,就是语法和使用规则的层面,不过不只探讨规则,更辅助以示例的分析。html
宏观层面,也就是对 PHP 语言自己的性能分析又分为三个方面:算法
##1、PHP 做为解释性语言的性能分析与提高编程
PHP 做为一门脚本语言,也是解释性语言,是其自然性能受限的缘由,由于同编译型语言在运行以前编译成二进制代码不一样,解释性语言在每一次运行都面对原始脚本的输入、解析、编译,而后执行。以下是 PHP 做为解释性语言的执行过程。缓存
如上所示,从上图能够看到,每一次运行,都须要经历三个解析、编译、运行三个过程。性能优化
那优化的点在哪里呢?能够想见,只要代码文件肯定,解析到编译这一步都是肯定的,由于文件已再也不变化,而执行,则因为输入参数的不一样而不一样。在性能优化的世界里,至上绝招就是在得到一样结果的状况下,减小操做,这就是大名鼎鼎的缓存。缓存无处不在,缓存也是性能优化的杀手锏。因而乎 OpCode 缓存这一招就出现了,只有第一次须要解析和编译,而在后面的执行中,直接由脚本到 Opcode,从而实现了性能提速。执行流程以下图所示:服务器
相对每一次解析、编译,读到脚本以后,直接从缓存读取字节码的效率会有大幅度的提高,提高幅度到底有多大呢?并发
咱们来作一个没有 Opcode 缓存的实验。20 个并发,总共 10000 次请求没有通过 opcode 缓存的请求,,获得以下结果:框架
其次,咱们在服务器上打开 Opcode 缓存。要想实现 opcode 缓存,只须要安装 APC、Zend OPCache、eAccelerator 扩展便可,即便安装了多个,也只启用其中一个。注意的是,修改了 php.ini 配置以后,须要从新加载 php-fpm 的配置。编程语言
这里分别启用 APC 和 Zend OPCache 作实验。启用 APC 的版本。
能够看到,速度有了较大幅度的提高,原来每一个请求 110ms,每秒处理请求 182 个,启用了 APC 以后 68ms,每秒处理请求 294 个,提高速度将近 40%。
在启用了 Zend Opcache 的版本中,获得同 APC 大体至关的结果。每秒处理请求 291 个,每请求耗时 68.5ms。
从上面的这个实验能够看到,所用的测试页面,有 40ms 以上的时间花在了语法解析和编译这两项上。经过将这两个操做缓存,能够将这个处理过程的速度大大提高。
这里附加补充一下,OpCode 究竟是什么东东,OpCode 编译以后的字节码,咱们可使用bytekit 这样的工具,或者使用 vld PHP 扩展来实现对 PHP 的代码编译。以下是 vld 插件解析代码的运行结果。
能够看到每一行代码被编译成相应的 OpCode 的输出。
##2、PHP 做为动态类型语言的性能分析与改进
第二个是 PHP 语言是动态类型的语言,动态类型的语言自己因为涉及到在内存中的类型推断,好比在 PHP 中,两个整数相加,咱们能获得整数值,一个整数和一个字符串相加,甚至两个字符串相加,都变成整数相加。而字符串和任何类型链接操做都成了字符串。
<?php $a = 10.11; $b = "30"; var_dump($a+$b); var_dump("10"+$b); var_dump(10+"20"); var_dump("10"+"20");
运行结果以下:
float(40.11) int(40) int(30) int(30)
语言的动态类型为开发者提供了方便,语言自己则会由于动态类型而下降效率。在 Swift 中,有一个特性叫类型推断,咱们能够看看类型推断会带来多大的一个效率上的差异呢?对于须要类型推断与不须要类型推断两段 Swift 代码,咱们尝试编译一下看看效果如何。 第一段代码以下:
这是一段 Swift 代码,字典只有 14 个键值对,这段代码的编译,9 分钟了尚未编译完成(5G 内存,2.4GHz CPU),编译环境为 Swift 1.2,Xcode 6.4。
可是若是调整代码以下:
也就是加上了类型限定,避免了 planeLocation 的类型推断。编译过程花了 2S 。
可见,做为动态类型附加的类型推断操做极大地下降了程序的编译速度。 固然,这个例子有点极端,用 Swift 来类比 PHP 也不必定合适,由于 Swift 语言自己也还在不断的进化过程当中。本例子只是代表在编程语言中,若是是动态类型语言,就涉及到对动态类型的处理,从编译的角度讲是会受影响的。
那么做为动态类型的 PHP 的效率如何提高呢?从 PHP 语言自己这个层面是没有办法解决的,由于你怎么写也是动态类型的代码。解决办法就是将PHP转化为静态类型的表示,也就是作成扩展,能够看到,鸟哥的不少项目,好比 Yaf 框架,都是作成了扩展的,固然这也是因为鸟哥是 C 高手。扩展因为是 C 或者 C++ 而写,因此再也不是动态类型,又加之是编译好的,而 C 语言自己的效率也会提高不少。因此效率会大幅度提升。
下面咱们来看一段代码,这段代码,只是实现了简单的素数运算,能计算指定值之内的素数个数,用的是普通的筛选法。如今看看扩展实现,跟 PHP 原生实现的效率差异,这个差异固然,不只仅是动态类型和编译类型的差异,还有语言效率的差异。
首先是用纯 PHP 写成的算法,计算 1000 万之内的素数个数,耗时在 33s 上下,实验了三次,获得的结果基本相同。
其次,咱们将这个求素数个数的过程,编写成了 PHP 扩展,在扩展中实现了 get_prime_numbers 函数,输入一个整数,返回小于该整数的素数。获得的结果以下,这个效率的提高是很是惊人的,在 1.4s 上下即返回。速度提高 20 倍以上。
能够想见,静态和编译类型的语言,其效率获得了惊人的提高。本程序的 C 语言代码以下:
PHP_FUNCTION(get_prime_numbers) { long value; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &value) == FAILURE) { return; } int *numbers = (int *)malloc(sizeof(int)*128*10000); memset(numbers, 0x0, 128*10000); int num = 2; numbers[0] = 2; numbers[1] = 3; bool flag = true; double f = 0; int i = 0; int j = 0; for(i=5; i<=value; i+=2) { flag = true; f = sqrt(i); for(j=0; j<num;j++) { if(i%numbers[j]==0) { flag = false; break; } if(numbers[j]>f) { break; } } if(flag) { numbers[num] = i; num++; } } free(numbers); RETURN_LONG(num); }
##3、PHP 语言自己底层性能引擎提高
第三个性能优化层面是语言自己的性能提高,这个就不是咱们普通开发者所能作的了。在 PHP 7之前,寄但愿于小版本的改进,可是改进幅度不是很是的显著,好比 PHP 5.3 、PHP 5.四、PHP 5.五、PHP 5.5 对同一段代码的性能比较,有必定程度的进步。
PHP 5.3 的版本在上面的例子中已讲过,须要 33s 左右的时间,咱们如今来看别的PHP版本。分别运行以下:
PHP 5.4 版,相较 5.3 版已经有必定程度的提高。快 6 秒左右。
PHP 5.5 版在 PHP 5.4的基础上又进了一步,快了 6S。
PHP5.6 反而有些退步。
PHP 7 果然是效率提高惊人,是 PHP5.3 的 3 倍以上。
以上是求素数脚本在各个 PHP 版本之间的运行速度区别,尽管只测试了这一个程序,也不是特别的严谨,可是这是在同一台机器上,并且编译 configure 参数也基本同样,仍是有必定可比性的。
在宏观层面,除了上面的这些以外,在实际的部署过程当中,对 PHP 性能的优化,还体现为要减小在运行中所消耗的资源。因此 FastCGI 模式和 mod_php 的模式比传统的 CGI 模式也更为受欢迎。由于在传统的 CGI 模式中,在每一次脚本运行都须要加载全部的模块。而在程序运行完成了以后,也要释放模块资源。以下图所示:
而在 FastCGI 和 mod_php 模式中,则不须要如此。只有 php-fpm 或者 Apache 启动的时候,须要加载一次全部的模块,在具体的某次运行过程当中,并不须要再次加载和释放相关的模块资源。
这样程序性能的效率获得了提高。以上就是有关 PHP 宏观层面的性能优化的分析,在本文的第二部分咱们将探讨应用方面的 PHP 优化准则。敬请期待!
本文系 OneAPM 工程师编译整理。OneAPM 是应用性能管理领域的新兴领军企业,能帮助企业用户和开发者轻松实现:缓慢的程序代码和 SQL 语句的实时抓取。想阅读更多技术文章,请访问 OneAPM 官方博客。