




已阅读5页,还剩1页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
PHP中函数的运行机制与实现原理 娇滴滴假发在任何语言中,函数都是最基本的组成单元。对于php的函数,它具有哪些特点?函数调用是怎么实现的?php函数的性能如何,有什么使用建 议?本文将从原理出发进行分析结合实际的性能测试尝试对这些问题进行回答,在了解实现的同时更好的编写php程序。同时也会对一些常见的php函数进行介 绍。PHP函数的分类在php中,横向划分的话,函数分为两大类:user function(内置函数)和internal function(内置函数)。前者就是用户在程序中自定义的一些函数和方法,后者则是php本身提供的各类库函数(比如sprintf、 array_push等)。用户也可以通过扩展的方法来编写库函数,这个将在后面介绍。对于user function,又可以细分为function(函数)和method(类方法),本文中将就这三种函数分别进行分析和测试。php函数的实现一个php函数最终是如何执行,这个流程是怎么样的呢?要回答这个问题,我们先来看看php代码的执行所经过的流程。从上图可以看到,php实现了一个典型的动态语言执行过程:拿到一段代码后,经过词法解析、语法解析等阶段后,源程序会被翻译成一个个指令 (opcodes),然后ZEND虚拟机顺次执行这些指令完成操作。Php本身是用c实现的,因此最终调用的也都是c的函数,实际上,我们可以把php看 做是一个c开发的软件。通过上面描述不难看出,php中函数的执行也是被翻译成了opcodes来调用,每次函数调用实际上是执行了一条或多条指令。对于每一个函数,zend都通过以下的数据结构来描述:其中type标明了函数的类型:用户函数、内置函数、重载函数。Common中包含函数的基本信息,包括函数名,参数信息,函数标志(普通函数、静态方法、抽象方法)。内置函数内置函数,其本质上就是真正的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内置函数实现的尽量不要自己重新写函数去实现。类方法类方法其执行原理和用户函数是相同的,也是翻译成opcodes顺次调用。类的实现,zend用一个数据结构zend_class_entry来实现,里面保存了类相关的一些基本信息。这个entry是在php编译的时候就已经处理完成。在zend_function的common中,有一个成员叫做scope,其指向的就是当前方法对应类的 zend_class_entry。关于php中面向对象的实现,这里就不在做更详细的介绍,今后将专门写一篇文章来详述php中面向对象的实现原理。就 函数这一块来说,method实现原理和function完全相同,理论上其性能也差不多,后面我们将做详细的性能对比。常用php函数实现及介绍 countcount是我们经常用到的一个函数,其功能是返回一个数组的长度。count这个函数,其复杂度是多少呢?一种常见的说法是count函数会遍历整个数组然后求出元素个数,因此复杂度是O(n)。那实际情况是不是这样呢?我们回到count的实现来看一下,通过源码可以发现,对于数组的count操作,函数最终的路径是zif_count- php_count_recursive- zend_hash_num_elements,而zend_hash_num_elements的行为是 return ht-nNumOfElements,可见,这是一个O(1)而不是O(n)的操作。实际上,数组在php底层就是一个hash_table,对 于hash表,zend中专门有一个元素nNumOfElements记录了当前元素的个数,因此对于一般的count实际上直接就返回了这个值。由此, 我们得出结论: count是O(1)的复杂度,和具体数组的大小无关。非数组类型的变量,count的行为时怎样?对于未设置变量返回0,而像int、double、string等则会返回1。 strlenStrlen用于返回一个字符串的长度。那么,他的实现原理是如何的呢?我们都知道在c中strlen是一个o(n)的函数,会顺序遍历字符串直到遇到0,然后出长度。Php中是否也这样呢?答案是否 定的,php里字符串是用一个复合结构来描述,包括指向具体数据的指针和字符串长度(和c+中string类似),因此strlen就直接返回字符串长 度了,是常数级别的操作。另外,对于非字符串类型的变量调用strlen,它会首先将变量强制转换为字符串再求长度,这点需要注意。 isset和array_key_exists这两个函数最常见的用法都是判断一个key是否在数组中存在。但是前者还可以用于判断一个变量是否被设置过。如前文所述,isset并非真正的函数,因此它的效率会比后者高很多。推荐用它代替array_key_exists。 array_push和array两者都是往数组尾部追加一个元素。不同的是前者可以一次push多个。他们最大的区别在于一个是函数一个是语言结构,因此后者效率要更高。因此如果只是普通的追加元素,建议使用array。 rand和mt_rand两者都是提供产生随机数的功能,前者使用libc标准的rand。后者用了 Mersenne Twister 中已知的特性作为随机数发生器,它可以产生随机数值的平均速度比 libc 提供的 rand() 快四倍。因此如果对性能要求较高,可以考虑用mt_rand代替前者。我们都知道,rand产生的是伪随机数,在C中需要用srand显示指定种子。但是在php中,rand会自己帮你默认调用一次srand,一般情况下不需要自己再显示的调用。需要注意的是,如果特殊情况下需要调用srand时,一定要配套调用。就是说srand对于rand,mt_srand对应srand,切不可混合使用,否则是无效的。 sort和usort两者都是用于排序,不同的是前者可以指定排序策略,类似我们C里面的qsort和C+的sort。在排序上两者都是采用标准的快排来实现,对于有排序需求的,如非特殊情况调用php提供的这些方法就可以了,不用自己重新实现一遍,效率会低很多。原因见前文对于用户函数和内置函数的分析比对。 urlencode和rawurlencode这两个都是用于url编码, 字符串中除了 -_. 之外的所有非字母数字字符都将被替换成百分号(%)后跟两位十六进制数。两者唯一的区别在于对于空格,urlencode会编码为+,而rawurlencode会编码为%20。一般情况下除了搜索引擎,我们的策略都是空格编码为%20。因此采用后者的居多。注意的是encode和decode系列一定要配套使用。 strcmp系列函数这一系列的函数包括strcmp、strncmp、strcasecmp、strncasecmp,实现功能和C函数相同。但也有不同,由于php的字符串是允许0出现,因此在判断的时候底层使用的是memcmp系列而非strcmp,理论上来说更快。另外由于php直接能获取到字符串长度,因此会首先这方面的检查,很多情况下效率就会高很多了。 is_int和is_numeric这两个函数功能相似又不完全相同,使用的时候一定需要注意他们的区别。Is_int:判断一个变量类型是否是整数型,php变量中专门有一个字段表征类型,因此直接判断这个类型即可,是一个绝对O(1)的操作。Is_numeric:判断一个变量是否是整数或数字字符串,也就是说除了整数型变量会返回true之外,对于字符串变量,如果形如”1234”,”1e4”等也会被判为true。这个时候会遍历字符串进行判断。总结及建议通过对函数实现的原理分析和性能测试,我们总结出以下一些结论:1 PHP的函数调用开销相对较大。2 函数相关信息保存在一个大的hash_table中,每次调用时通过函数名在hash表中查找,因此函数名长度对性能也有一定影响。3 函数返回引用没有实际意义。4 内置php函数性能比用户函数高很多,尤其对于字符串类操作。5 类方法、普通函数、静态方法效率几乎相同,没有太大差异。6 除去空函数调用的影响,内置函数和同样功能的C函数性能基本差不多。7 所有的参数传递都是采用引用计数的浅拷贝,代价很小。8 函数个数对性能影响几乎可以忽略。因此,对于php函数的使用,有如下一些建议:9 一个功能可以用内置函数完成,尽量使用它而不是自己编写php函数。10 如果某个功能对性能要求很高,可以考虑用扩展来实现。11 PHP函数调用开销较大,因此不要过分封装。有些功能,如果需要调用的次数很多本身又只用1、2行代码就行实现的,建议就不要封装调用了。12 不要过分迷恋各种设计模式,如上一条描述,过分的封装会带来性能的下降。需要考虑两者的权衡。PHP有自己的特点,切不可
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 客户维护与管理制度
- 宵夜档老板管理制度
- 家居定制店管理制度
- 库房调发货管理制度
- 影像科仪器管理制度
- 微信管理群管理制度
- 德州小餐桌管理制度
- 快印店质量管理制度
- 总公司卫生管理制度
- 总经理薪资管理制度
- 安徽省安庆望江县联考2025年七年级英语第二学期期中质量检测模拟试题含答案
- 森林草原防火 无人机巡查技术规范 编制说明
- 2025年江苏省苏州吴中、吴江、相城区初三英语一模试题及答案
- 智能化汽车中的专利战略布局-洞察阐释
- 不寐的中医护理常规
- 2024年新疆维吾尔自治区、新疆生产建设兵团中考语文试卷(含答案与解析)
- 2025至2030年中国精致石英砂滤料行业投资前景及策略咨询报告
- 保育师(高级)职业技能鉴定参考试题(附答案)
- 高性能耐磨材料设计-全面剖析
- 2025-2030中国药食同源行业市场运行分析及市场前景预测研究报告
- 2024年杭州地铁科技有限公司招聘笔试真题
评论
0/150
提交评论