百度工程师讲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学习之PHP运算符
Oct 09 PHP
PHP+FLASH实现上传文件进度条相关文件 下载
Jul 21 PHP
DedeCMS dede_channeltype表字段注释
Apr 07 PHP
PHP中的MYSQL常用函数(php下操作数据库必备)
Sep 12 PHP
PHP+Mysql+jQuery实现发布微博程序 jQuery篇
Oct 08 PHP
iis下php mail函数的sendmail配置方法(官方推荐)
Apr 25 PHP
destoon之一键登录设置
Jun 21 PHP
Laravel中使用自己编写类库的3种方法
Feb 10 PHP
PHP读取PPT文件的方法
Dec 10 PHP
php redis实现对200w用户的即时推送
Mar 04 PHP
php下的原生ajax请求用法实例分析
Feb 28 PHP
goto语法在PHP中的使用教程
Sep 17 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
《猛禽小队》:DC宇宙的又一超级大烂片
2020/04/09 欧美动漫
DOTA2 无惧惊涛骇浪 昆卡大型水友攻略
2020/04/20 DOTA
德生PL330的评价与改造
2021/03/02 无线电
php jsonp单引号转义
2014/11/23 PHP
WordPress中用于检索模版的相关PHP函数使用解析
2015/12/15 PHP
PHP基于MySQLI函数封装的数据库连接工具类【定义与用法】
2017/08/11 PHP
thinkphp ajaxfileupload实现异步上传图片的示例
2017/08/28 PHP
PHP7基于curl实现的上传图片功能
2018/05/11 PHP
php判断电子邮件是否正确方法
2018/12/04 PHP
ThinkPHP3.2.3框架邮件发送功能图文实例详解
2019/04/23 PHP
JavaScript基本概念初级讲解论坛贴的学习记录
2009/02/22 Javascript
javascript操作JSON的要领总结
2012/12/09 Javascript
jQuery实现等比例缩放大图片让大图片自适应页面布局
2013/10/16 Javascript
javascript弹出页面回传值的方法
2015/01/28 Javascript
jQuery 弹出层插件(推荐)
2016/05/24 Javascript
jquery  实现轮播图详解及实例代码
2016/10/12 Javascript
使用JavaScript为一张图片设置备选路径的方法
2017/01/04 Javascript
jquery中绑定事件的异同
2017/02/28 Javascript
echart简介_动力节点Java学院整理
2017/08/11 Javascript
AngularJs导出数据到Excel的示例代码
2017/08/11 Javascript
JS原生瀑布流效果实现
2019/04/26 Javascript
[01:50:49]DOTA2-DPC中国联赛 正赛 PSG.LGD vs Aster BO3 第三场 1月24日
2021/03/11 DOTA
[01:16:50]DOTA2-DPC中国联赛 正赛 Phoenix vs CDEC BO3 第一场 3月7日
2021/03/11 DOTA
Python网络爬虫出现乱码问题的解决方法
2017/01/05 Python
python高级特性和高阶函数及使用详解
2018/10/17 Python
Python 3 判断2个字典相同
2019/08/06 Python
平面设计岗位职责
2013/12/14 职场文书
房地产促销活动方案
2014/03/01 职场文书
财务科科长岗位职责
2014/03/10 职场文书
2014年保管员工作总结
2014/11/18 职场文书
2014年纳税评估工作总结
2014/12/23 职场文书
优秀毕业生主要事迹材料
2015/11/04 职场文书
少先大队干部竞选稿
2015/11/20 职场文书
sql查询结果列拼接成逗号分隔的字符串方法
2021/05/25 SQL Server
vue postcss-px2rem 自适应布局
2022/05/15 Vue.js
使用CSS自定义属性实现骨架屏效果
2022/06/21 HTML / CSS