百度工程师讲PHP函数的实现原理及性能分析(一)


Posted in PHP onMay 13, 2015

前言

在任何语言中,函数都是最基本的组成单元。对于php的函数,它具有哪些特点?函数调用是怎么实现的?php函数的性能如何,有什么使用建议?本文将从原理出发进行分析结合实际的性能测试尝试对这些问题进行回答,在了解实现的同时更好的编写php程序。同时也会对一些常见的php函数进行介绍。

php函数的分类

在php中,横向划分的话,函数分为两大类: user function(内置函数) 和internal function(内置函数)。前者就是用户在程序中自定义的一些函数和方法,后者则是php本身提供的各类库函数(比如sprintf、array_push等)。用户也可以通过扩展的方法来编写库函数,这个将在后面介绍。对于user function,又可以细分为function(函数)和method(类方法),本文中将就这三种函数分别进行分析和测试。

php函数的实现

一个php函数最终是如何执行,这个流程是怎么样的呢?
要回答这个问题,我们先来看看php代码的执行所经过的流程。

百度工程师讲PHP函数的实现原理及性能分析(一)

从图1可以看到,php实现了一个典型的动态语言执行过程:拿到一段代码后,经过词法解析、语法解析等阶段后,源程序会被翻译成一个个指令(opcodes),然后ZEND虚拟机顺次执行这些指令完成操作。Php本身是用c实现的,因此最终调用的也都是c的函数,实际上,我们可以把php看做是一个c开发的软件。通过上面描述不难看出,php中函数的执行也是被翻译成了opcodes来调用,每次函数调用实际上是执行了一条或多条指令。

对于每一个函数,zend都通过以下的数据结构来描述

typedef union _zend_function { 

zend_uchar type; /* MUST be the first element of this struct! */ 

struct { 

zend_uchar type; /* never used */ 

char *function_name; 

zend_class_entry *scope; 

zend_uint fn_flags; 

union _zend_function *prototype; 

zend_uint num_args; 

zend_uint required_num_args; 

zend_arg_info *arg_info; 

zend_bool pass_rest_by_reference; 

unsigned char return_reference; 

} common;
zend_op_array op_array; 

zend_internal_function internal_function; 

} zend_function;


typedef struct _zend_function_state { 

HashTable *function_symbol_table; 

zend_function *function; 

void *reserved[ZEND_MAX_RESERVED_RESOURCES]; 

} zend_function_state;

其中type标明了函数的类型:用户函数、内置函数、重载函数。Common中包含函数的基本信息,包括函数名,参数信息,函数标志(普通函数、静态方法、抽象方法)等内容。另外,对于用户函数,还有一个函数符号表,记录了内部变量等,这个将在后面详述。 Zend维护了一个全局function_table,这是一个大的hahs表。函数调用的时候会首先根据函数名从表中找到对应的zend_function。当进行函数调用时候,虚拟机会根据type的不同决定调用方法, 不同类型的函数,其执行原理是不相同的 。

内置函数

内置函数,其本质上就是真正的c函数,每一个内置函数,php在最终编译后都会展开成为一个名叫zif_xxxx的function,比如我们常见的sprintf,对应到底层就是zif_sprintf。Zend在执行的时候,如果发现是内置函数,则只是简单的做一个转发操作。
Zend提供了一系列的api供调用,包括参数获取、数组操作、内存分配等。内置函数的参数获取,通过zend_parse_parameters方法来实现,对于数组、字符串等参数,zend实现的是浅拷贝,因此这个效率是很高的。可以这样说,对于php内置函数,其效率和相应c函数几乎相同,唯一多了一次转发调用。

内置函数在php中都是通过so的方式进行动态加载,用户也可以根据需要自己编写相应的so,也就是我们常说的扩展。ZEND提供了一系列的api供扩展使用

用户函数

和内置函数相比,用户通过php实现的自定义函数具有完全不同的执行过程和实现原理。如前文所述,我们知道php代码是被翻译成为了一条条opcode来执行的,用户函数也不例外,实际中每个函数对应到一组opcode,这组指令被保存在zend_function中。于是,用户函数的调用最终就是对应到一组opcodes的执行。

》》局部变量的保存及递归的实现

我们知道,函数递归是通过堆栈来完成的。在php中,也是利用类似的方法来实现。Zend为每个php函数分配了一个活动符号表(active_sym_table),记录当前函数中所有局部变量的状态。所有的符号表通过堆栈的形式来维护,每当有函数调用的时候,分配一个新的符号表并入栈。当调用结束后当前符号表出栈。由此实现了状态的保存和递归。
对于栈的维护,zend在这里做了优化。预先分配一个长度为N的静态数组来模拟堆栈,这种通过静态数组来模拟动态数据结构的手法在我们自己的程序中也经常有使用,这种方式避免了每次调用带来的内存分配、销毁。ZEND只是在函数调用结束时将当前栈顶的符号表数据clean掉即可。因为静态数组长度为N,一旦函数调用层次超过N,程序不会出现栈溢出,这种情况下zend就会进行符号表的分配、销毁,因此会导致性能下降很多。在zend里面,N目前取值是32。因此,我们编写php程序的时候,函数调用层次最好不要超过32。当然,如果是web应用,本身可以函数调用层次的深度。

》》参数的传递 和内置函数调用zend_parse_params来获取参数不同,用户函数中参数的获取是通过指令来完成的。函数有几个参数就对应几条指令。具体到实现上就是普通的变量赋值。通过上面的分析可以看出,和内置函数相比,由于是自己维护堆栈表,而且每条指令的执行也是一个c函数,用户函数的性能相对会差很多,后面会有具体的对比分析。因此,如果一个功能有对应php内置函数实现的尽量不要自己重新写函数去实现。

PHP 相关文章推荐
PHP数据库操作面向对象的优点
Oct 09 PHP
PHP的FTP学习(二)[转自奥索]
Oct 09 PHP
php连接mssql数据库的几种方法
Feb 21 PHP
php伪静态之APACHE篇
Jun 02 PHP
php实现的太平洋时间和北京时间互转的自定义函数分享
Aug 19 PHP
PHP中UNIX时间戳和日期间的转换与计算实例
Nov 19 PHP
php面向对象中static静态属性和静态方法的调用
Feb 08 PHP
PHP中的Session对象如何使用
Sep 25 PHP
解决PHP里大量数据循环时内存耗尽的方法
Oct 10 PHP
php 修改上传文件大小限制实例详解
Oct 23 PHP
laravel 实现设置时区的简单方法
Oct 10 PHP
TP3.2.3框架文件上传操作实例详解
Jan 23 PHP
PHP版本如何选择?应该使用哪个版本?
May 13 #PHP
PHP Hash算法:Times33算法代码实例
May 13 #PHP
你应该知道PHP浮点数知识
May 13 #PHP
PHP浮点数精度问题汇总
May 13 #PHP
PHP生成器简单实例
May 13 #PHP
php实现比较两个字符串日期大小的方法
May 12 #PHP
php使用substr()和strpos()联合查找字符串中某一特定字符的方法
May 12 #PHP
You might like
透析PHP的配置文件php.ini
2006/10/09 PHP
用PHP函数解决SQL injection
2006/12/09 PHP
php读取javascript设置的cookies的代码
2010/04/12 PHP
php allow_url_include的应用和解释
2010/04/22 PHP
php启用zlib压缩文件的配置方法
2013/06/12 PHP
用于table内容排序
2006/07/21 Javascript
用jscript实现新建和保存一个word文档
2007/06/15 Javascript
js null,undefined,字符串小结
2010/08/21 Javascript
学习从实践开始之jQuery插件开发 菜单插件开发
2012/05/03 Javascript
jQuery中.live()方法的用法深入解析
2013/12/30 Javascript
Node.js的包详细介绍
2015/01/14 Javascript
详谈javascript中DOM的基本属性
2015/02/26 Javascript
完善的jquery处理机制
2016/02/21 Javascript
Bootstrap前端开发案例二
2016/06/17 Javascript
浅析上传头像示例及其注意事项
2016/12/14 Javascript
利用原生JS与jQuery实现数字线性变化的动画
2017/02/24 Javascript
详谈Angular路由与Nodejs路由的区别
2017/03/05 NodeJs
Angularjs验证用户输入的字符串是否为日期时间
2017/06/01 Javascript
js实现移动端轮播图效果
2020/12/09 Javascript
浅谈react.js中实现tab吸顶效果的问题
2017/09/06 Javascript
微信小程序列表时间戳转换实现过程解析
2019/10/12 Javascript
浅谈python中str字符串和unicode对象字符串的拼接问题
2018/12/04 Python
opencv python 图像轮廓/检测轮廓/绘制轮廓的方法
2019/07/03 Python
Python中typing模块与类型注解的使用方法
2019/08/05 Python
matplotlib相关系统目录获取方式小结
2021/02/03 Python
HTML5中indexedDB 数据库的使用实例
2017/05/11 HTML / CSS
泰国折扣酒店预订:Hotels2Thailand
2018/03/20 全球购物
献爱心捐款倡议书
2014/05/14 职场文书
高中班级口号
2014/06/09 职场文书
2014银行领导班子四风对照检查材料思想汇报
2014/09/25 职场文书
2014年政风行风评议工作总结
2014/10/21 职场文书
2015元旦联欢晚会结束语
2014/12/14 职场文书
高考百日冲刺决心书
2015/09/23 职场文书
幼儿园小班开学寄语(2016秋季)
2015/12/03 职场文书
《夹竹桃》教学反思
2016/02/23 职场文书
怎样写工作总结啊!
2019/06/18 职场文书