百度工程师讲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递归调用的小技巧讲解
Feb 19 PHP
修改php.ini不生效问题解决方法(上传大于8M的文件)
Jun 14 PHP
php生成excel文件的简单方法
Feb 08 PHP
PHP产生不重复随机数的5个方法总结
Nov 12 PHP
php_imagick实现图片剪切、旋转、锐化、减色或增加特效的方法
Dec 15 PHP
PHP下使用mysqli的函数连接mysql出现warning: mysqli::real_connect(): (hy000/1040): ...
Feb 14 PHP
php基于Fleaphp框架实现cvs数据导入MySQL的方法
Feb 23 PHP
ThinkPHP模板循环输出Volist标签用法实例详解
Mar 23 PHP
PHP+JS实现的商品秒杀倒计时用法示例
Nov 15 PHP
php读取本地json文件的实例
Mar 07 PHP
ThinkPHP5+UEditor图片上传到阿里云对象存储OSS功能示例
Aug 05 PHP
Laravel 解决419错误 -ajax请求错误的问题(CSRF验证)
Oct 25 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读取mysql乱码,用set names XXX解决的原理分享
2011/12/29 PHP
浅谈PHP中其他类型转化为Bool类型
2016/03/28 PHP
javascript中最常用的继承模式 组合继承
2010/08/12 Javascript
JQuery魔力之$("tagName")与selector
2012/03/05 Javascript
JavaScript定时器详解及实例
2013/08/01 Javascript
jquery重新播放css动画所遇问题解决
2013/08/21 Javascript
js编写当天简单日历效果【实现代码】
2016/05/03 Javascript
jquery实现无刷新验证码的简单实例
2016/05/19 Javascript
jquery实现简单的瀑布流布局
2016/12/11 Javascript
AngularJS中$http的交互问题
2017/03/29 Javascript
详解JavaScript对象的深浅复制
2017/03/30 Javascript
jquery Form轻松实现文件上传
2017/05/24 jQuery
Vue2.0用 watch 观察 prop 变化(不触发)
2017/09/08 Javascript
vue项目总结之文件夹结构配置详解
2017/12/13 Javascript
Linux Centos7.2下安装nodejs&npm配置全局路径的教程
2018/05/15 NodeJs
详解keep-alive + vuex 让缓存的页面灵活起来
2019/04/19 Javascript
uniapp开发小程序实现滑动页面控制元素的显示和隐藏效果
2020/12/10 Javascript
js实现鼠标拖曳效果
2020/12/30 Javascript
[02:12]DOTA2英雄基础教程 变体精灵
2013/12/16 DOTA
跟老齐学Python之眼花缭乱的运算符
2014/09/14 Python
详谈python http长连接客户端
2017/06/12 Python
hmac模块生成加入了密钥的消息摘要详解
2018/01/11 Python
python 按照固定长度分割字符串的方法小结
2018/04/30 Python
python原类、类的创建过程与方法详解
2019/07/19 Python
python使用Thread的setDaemon启动后台线程教程
2020/04/25 Python
基于python实现把json数据转换成Excel表格
2020/05/07 Python
让IE9以下版本的浏览器兼容HTML5的方法
2014/03/12 HTML / CSS
说出数据连接池的工作机制是什么?
2013/04/19 面试题
大学社团计划书
2014/05/01 职场文书
广播体操口号
2014/06/18 职场文书
给老婆的保证书
2015/01/16 职场文书
2016年教师节感恩寄语
2015/12/04 职场文书
观看《筑梦中国》纪录片心得体会
2016/01/18 职场文书
一年之计:2019年下半年的计划
2019/05/07 职场文书
python 实现图与图之间的间距调整subplots_adjust
2021/05/21 Python
关于redisson缓存序列化几枚大坑说明
2021/08/04 Redis