深入解析PHP类方法的执行原理与性能对比分析
百度工程师谈PHP函数的实现原理及性能分析

类方法
类方法的执行原理与用户函数相同,也是翻译成操作码顺序调用。类的实现是由zend使用一个数据结构zend_class_entry来实现的,该结构存储了与类相关的一些基本信息。编译 PHP 时会处理此条目。
在zend_function的common中,有一个名为scope的成员,它指向当前方法对应的类的zend_class_entry。关于PHP中面向对象的实现,这里就不做更详细的介绍了。以后我会专门写一篇文章来详细介绍PHP中面向对象的实现原理。就函数而言,方法的实现原理与函数完全相同,其性能在理论上也类似。稍后我们会进行详细的性能对比。
性能对比
函数名称长度对性能的影响
》》测试方法比较名称长度为1、2、4、8、16的函数,测试比较它们每秒可以执行的次数,以确定函数名称长度对性能的影响。
》》测试结果如下图
》》结果分析
从图中可以看出,函数名的长度对性能还是有一定影响的。长度为 1 的函数和长度为 16 的空函数调用的性能差异为 1 倍。通过分析源码不难找到原因。上面提到,当一个函数被调用时,zend首先会在一个全局的function_table(一个哈希表)中通过函数名查询相关信息。不可避免地,名称越长,查询所需的时间就越长。因此,在实际编写程序时,建议多次调用的函数名称不要太长。
虽然函数名的长度对性能有一定的影响,但是具体是多大呢?这个问题还是要根据实际情况来考虑。如果一个功能本身比较复杂,对于整体性能不会有太大的影响。一种建议是,对于调用次数较多、功能相对简单的函数,给予简洁明了的名称。
函数数量对性能的影响
》》测试方法
在以下三种环境下进行函数调用测试并分析结果: 1. 程序仅包含 1 个函数 2. 程序包含 100 个函数 3. 程序包含 1000 个函数。测试这三种情况下每秒可以调用的函数数量
》》测试结果如下图
》》结果分析
从测试结果可以看出,这三种情况下的性能几乎是一样的。当函数数量增加时,性能下降很小,可以忽略不计。从实现原理分析,几种实现方式唯一的区别就是功能获取部分。前面说过,所有函数都放在一个哈希表中,不同数字下查找效率应该还是接近O(1)的,所以性能差异并不大。
不同类型函数调用的成本
》》测试方法
选择用户函数、类方法、静态方法和内置函数之一。函数本身不做任何事情,直接返回。主要测试空函数调用的消耗。测试结果是每秒执行的次数。为了消除测试过程中的其他影响,所有函数名称的长度相同。
》》测试结果如下图
》》结果分析
从测试结果可以看出,对于用户自己编写的PHP函数,无论是什么类型,效率都相差无几,都在280w/s左右。正如我们所料,即使是空调,内置功能的效率也高得多,达到了780w/s,是前者的三倍。可以看到,内置函数调用的开销还是比用户函数低很多。从前面的原理分析可以看出,主要差距在于调用用户函数时初始化符号表、接收参数等操作。
内置函数和用户函数的性能对比
》》测试方法
为了比较内置函数和用户函数的性能,这里我们选取几个常用的函数,然后使用PHP函数来实现相同的功能来比较性能。测试过程中,我们从字符串、数学、数组中分别选取了一个典型的进行比较。这些函数是字符串截取(substr)、十进制到二进制转换(decbin)、最小值(min)和返回。数组 (array_keys) 中的所有键。
》》测试结果如下图
》》结果分析
从测试结果可以看出,正如我们预期的那样,内置功能的整体性能远高于普通用户功能。尤其是涉及字符串操作的函数,差距达到1个数量级。因此,使用函数的一个原则是,如果某个函数有对应的内置函数,就尽量使用它,而不是自己编写PHP函数。对于一些涉及大量字符串操作的函数,为了提高性能,可以考虑使用扩展。例如常见的富文本过滤等。
与C函数性能比较
》》测试方法
我们选择了三个函数分别用于字符串操作和算术操作进行比较,并且PHP是使用扩展来实现的。这三个函数分别是简单的一次性算术运算、字符串比较和多重算术运算。除了两类功能外,我们还将测试去除空调开销功能后的性能。一方面,我们比较了两个函数(C和PHP内置)的性能差异,另一方面,我们验证了空调函数的消耗测试点:执行100,000次操作的时间消耗
》》测试结果如下图
》》结果分析
除去php函数空调的影响后,内置函数和C函数的开销差别很小。随着功能越来越复杂,双方的性能趋于一致。从前面的功能实现分析中可以很容易地证明这一点。毕竟内置函数都是用C实现的,函数越复杂,C和PHP的性能差距就越小。与C相比,PHP函数调用的开销要高很多,而且简单函数的性能还是有一定影响的。因此,PHP中的函数不应该嵌套和封装得太深。
伪函数及其性能
在PHP中,有一些函数是标准函数用法,但底层实现与真正的函数调用完全不同。这些功能不属于上述三个功能中的任何一个。它们的本质是单个操作码,这里称为伪函数或指令函数。
如上所述,伪函数的使用方式与标准函数一样,并且看起来具有相同的特征。但当它们最终执行时,会被zend反射成相应的指令(操作码)进行调用,因此它们的实现更接近于if、for、算术运算等操作。
》》php中的伪函数
伊塞特
空的
未设置
评估
从上面的介绍可以看出,由于伪函数是直接翻译成指令执行的,因此与普通函数相比,少了一份函数调用带来的开销,因此性能会更好。我们通过下面的测试来进行对比。 Array_key_exists和isset都可以判断数组中是否存在某个key。我们来看看他们的表现吧。
从图中可以看出,与array_key_exists相比,isset性能要高很多,基本上是前者的4倍左右,甚至与空函数调用相比,其性能也高出1倍左右。这也证明PHP函数调用的开销还是比较大的。