百度工程师讲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 相关文章推荐
杏林同学录(九)
Oct 09 PHP
PHP获取163、gmail、126等邮箱联系人地址【已测试2009.10.10】
Oct 11 PHP
PHP 数组排序方法总结 推荐收藏
Jun 30 PHP
一些php技巧与注意事项分析
Feb 03 PHP
PHP学习之字符串比较和查找
Apr 17 PHP
PHP将整个网站生成HTML纯静态网页的方法总结
Feb 05 PHP
PHP缓存技术的多种方法小结
Aug 14 PHP
将FCKeditor导入PHP+SMARTY的实现方法
Jan 15 PHP
php实现格式化多行文本为Js可用格式
Apr 15 PHP
php如何获取文件的扩展名
Oct 28 PHP
PHP内置函数生成随机数实例
Jan 18 PHP
php 的多进程操作实践案例分析
Feb 28 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
apache rewrite_module模块使用教程
2008/01/10 PHP
php zend 相对路径问题
2009/01/12 PHP
PHP新手入门学习方法
2011/05/08 PHP
模板引擎smarty工作原理以及使用示例
2014/05/25 PHP
php+xml实现在线英文词典之添加词条的方法
2015/01/23 PHP
PHP支付系统设计与典型案例分享
2016/08/02 PHP
PHPMailer ThinkPHP实现自动发送邮件功能
2018/06/10 PHP
ThinkPHP框架整合微信支付之Native 扫码支付模式二图文详解
2019/04/09 PHP
function, new function, new Function之间的区别
2007/03/08 Javascript
在iframe里的页面编写js,实现在父窗口上创建动画效果展开和收缩的div(不变动iframe父窗口代码)
2011/12/20 Javascript
Ext中下拉列表ComboBox组件store数据格式用法介绍
2013/07/15 Javascript
jQuery prev ~ siblings选择器使用介绍
2013/08/09 Javascript
js 将json字符串转换为json对象的方法解析
2013/11/13 Javascript
jQuery中last()方法用法实例
2015/01/06 Javascript
js判断浏览器版本以及浏览器内核的方法
2015/01/20 Javascript
ECMAScript6函数剩余参数(Rest Parameters)
2015/06/12 Javascript
使用JavaScript实现连续滚动字幕效果的方法
2015/07/07 Javascript
jqTransform美化表单
2015/10/10 Javascript
详解jQuery向动态生成的内容添加事件响应jQuery live()方法
2015/11/02 Javascript
EasyUI创建对话框的两种方式
2016/08/23 Javascript
vuex 的简单使用
2018/03/22 Javascript
基于JS开发微信网页录音功能的实例代码
2019/04/30 Javascript
通过jQuery学习js类型判断的技巧
2019/05/27 jQuery
Django的信号机制详解
2017/05/05 Python
Python根据已知邻接矩阵绘制无向图操作示例
2018/06/23 Python
利用Python模拟登录pastebin.com的实现方法
2019/07/12 Python
python3 selenium自动化 下拉框定位的例子
2019/08/23 Python
Python利用FFT进行简单滤波的实现
2020/02/26 Python
SpringBoot实现登录注册常见问题解决方案
2020/03/04 Python
Python基于numpy模块实现回归预测
2020/05/14 Python
Python logging模块进行封装实现原理解析
2020/08/07 Python
Python django框架 web端视频加密的实例详解
2020/11/20 Python
俄罗斯购买自行车网站:Vamvelosiped
2021/01/29 全球购物
俄罗斯购买内衣网站:Trusiki
2020/08/22 全球购物
怎样写离婚协议书
2015/01/26 职场文书
出国留学导师推荐信
2015/03/26 职场文书