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

前言

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

php函数的分类

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

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内置函数实现的尽量不要自己重新写函数去实现。

(0)

相关推荐

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

    类方法 类方法其执行原理和用户函数是相同的,也是翻译成opcodes顺次调用.类的实现,zend用一个数据结构zend_class_entry来实现,里面保存了类相关的一些基本信息.这个entry是在php编译的时候就已经处理完成. 在 zend_function的common中,有一个成员叫做scope,其指向的就是当前方法对应类的zend_class_entry.关于php中面向对象的实现,这里就不在做更详细的介绍,今后将专门写一篇文章来详述php中面向对象的实现原理.就函数这一块来说,me

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

    常用php函数实现及介绍 count count是我们经常用到的一个函数,其功能是返回一个数组的长度. count这个函数,其复杂度是多少呢? 一种常见的说法是count函数会遍历整个数组然后求出元素个数,因此复杂度是O(n).那实际情况是不是这样呢?我们回到count的实现来看一下,通过源码可以发现,对于数组的count操作,函数最终的路径是zif_count-> php_count_recursive-> zend_hash_num_elements,而zend_hash_num_elem

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

    前言 在任何语言中,函数都是最基本的组成单元.对于php的函数,它具有哪些特点?函数调用是怎么实现的?php函数的性能如何,有什么使用建议?本文将从原理出发进行分析结合实际的性能测试尝试对这些问题进行回答,在了解实现的同时更好的编写php程序.同时也会对一些常见的php函数进行介绍. php函数的分类 在php中,横向划分的话,函数分为两大类: user function(内置函数) 和internal function(内置函数).前者就是用户在程序中自定义的一些函数和方法,后者则是php本身

  • Python函数装饰器原理与用法详解

    本文实例讲述了Python函数装饰器原理与用法.分享给大家供大家参考,具体如下: 装饰器本质上是一个函数,该函数用来处理其他函数,它可以让其他函数在不需要修改代码的前提下增加额外的功能,装饰器的返回值也是一个函数对象.它经常用于有切面需求的场景,比如:插入日志.性能测试.事务处理.缓存.权限校验等应用场景.装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用.概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能. 严格来说,装饰器只是语法糖,

  • 详解C++虚函数的工作原理

    静态绑定与动态绑定 讨论静态绑定与动态绑定,首先需要理解的是绑定,何为绑定?函数调用与函数本身的关联,以及成员访问与变量内存地址间的关系,称为绑定. 理解了绑定后再理解静态与动态. 静态绑定:指在程序编译过程中,把函数调用与响应调用所需的代码结合的过程,称为静态绑定.发生在编译期. 动态绑定:指在执行期间判断所引用对象的实际类型,根据实际的类型调用其相应的方法.程序运行过程中,把函数调用与响应调用所需的代码相结合的过程称为动态绑定.发生于运行期. C++中动态绑定 在C++中动态绑定是通过虚函数

  • Javascript自执行匿名函数(function() { })()的原理浅析

    函数是JavaScript中最灵活的一种对象,这里只是讲解其匿名函数的用途.匿名函数指没有指定函数名或指针的函数,自执行匿名函数只是其中一种,下文中称这种函数为:自执行函数 下面是一个最常见的自执行函数: // 传统匿名函数 (function() { alert('hello'); })(); 这段代码的执行效果就是在页面再载入时弹出:"hello" 是什么促使它自动执行的?,来看下面的代码 // 在传统写法上去掉小括号,并在前面加上运算符 ~,!,+,- ~function(){

  • 深入浅析同源与跨域,jsonp(函数封装),CORS原理

    目录 同源政策 Ajax请求限制: Ajax 只能向自己的服务器发送请求 同源: 同源政策的目的: 不受同源策略限制: 跨域问题 跨域的原因: 解决跨域问题: 使用 JSONP 解决 解决方法: JSONP JSONP的优缺点: JSONP代码优化: JSONP函数封装: CORS 使用CORS解决 Express框架中跨域的实现: 同源政策 Ajax请求限制: Ajax 只能向自己的服务器发送请求 同源策略是浏览器的一个安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源. 比

  • MySQL实例精讲单行函数以及字符数学日期流程控制

    目录 一.字符函数 1.大小写控制函数 2.字符控制函数 二.数学函数 三.日期函数 四.其他函数 五.流程控制函数 一.字符函数 1.大小写控制函数 ①UPPER():转换成大写 SELECT UPPER('Hello'); ②LOWER():转换成小写 SELECT LOWER('Hello'); 2.字符控制函数 ①LENGTH():获取参数值的字节个数 SELECT LENGTH('叶绿体不忘呼吸aaaa'); ②CONCAT():拼接字符串 SELECT CONCAT('Hello',

  • React函数组件hook原理及构建hook链表算法详情

    目录 写在前面的小结 Demo fiber hook 链表 hook 对象及其属性介绍 useState Hook 对象 useRef Hook useEffect.useLayoutEffect 以及 useImperativeHandle useMemo useCallback 构建 Hook 链表的源码 renderWithHooks 函数组件执行 构建 hook 链表的算法 mountWorkInProgressHook 构建 hook 链表算法 updateWorkInProgress

  • C++ 多态虚函数的底层原理深入理解

    目录 1 多态的基本概念 1.1 什么是多态? 1.2 怎么实现多态 2 虚函数的底层原理 1 多态的基本概念 1.1 什么是多态? 多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为,通常是父类调用子类的重写函数,在C++中就是 父类指针指向子类对象,此时父类指针的向下引用就可以实现多态 比如看下面的代码: class Animal { public: //虚函数 virtual void speak() { cout << "动物在说话" <<

随机推荐