![]()
图2 Sapi协议 这里介绍一下其中一些主要函数 5、Php的执行流程&opcode
图3 php代码的执行过程 从图上可以看到,php实现了一个典型的动态语言执行过程:拿到一段代码后,经过词法解析、语法解析等阶段后,源程序会被翻译成一个个指令(opcodes),然后ZEND虚拟机顺次执行这些指令完成操作。Php本身是用c实现的,因此最终调用的也都是c的函数,实际上,我们可以把php看做是一个c开发的软件。 通过上面描述不难看出,php的执行的核心是翻译出来的一条一条指令,也即opcode u opcode Opcode是php程序执行的最基本单位。一个opcode由两个参数(op1,op2)、返回值和处理函数组成。Php程序最终被翻译为一组opcode处理函数的顺序执行 常见的几个处理函数 ZEND_ASSIGN_SPEC_CV_CV_HANDLER : 变量分配 ($a=$b) ZEND_DO_FCALL_BY_NAME_SPEC_HANDLER:函数调用 ZEND_CONCAT_SPEC_CV_CV_HANDLER:字符串拼接 $a.$b ZEND_ADD_SPEC_CV_CONST_HANDLER: 加法运算 $a+2 ZEND_IS_EQUAL_SPEC_CV_CONST:判断相等 $a==1 ZEND_IS_IDENTICAL_SPEC_CV_CONST:判断相等 $a===1 6、HashTable — 核心数据结构 php的hash table具有如下特点: 支持典型的key->value查询 Zend hash table实现了典型的hash表散列结构,同时通过附加一个双向链表,提供了正向、反向遍历数组的功能。其结构如下图
图4 zend hash table数据结构 可以看到,在hash table中既有key->value形式的散列结构,也有双向链表模式,使得它能够非常方便的支持快速查找和线性遍历。 散列结构 另外,在进行key->value快速查找时候,zend本身还做了一些优化,通过空间换时间的方式加快速度。比如在每个元素中都会用一个变量nKeyLength标识key的长度以作快速判定。 双向链表 Php关联数组 getKeyHashValue h; index = n & nTableMask; Bucket *p = arBucket[index]; while (p) { if ((p->h == h) && (p->nKeyLength == nKeyLength)) { RETURN p->data; } p=p->next; } RETURN FALTURE; 从代码可以看出,这是一个常见的hash查询过程并增加一些快速判定加速查找。 Php索引数组 Zend HashTable内部进行了归一化处理,对于index类型key同样分配了hash值和nKeyLength(为0)。内部成员变量nNextFreeElement就是当前分配到的最大id,每次push后自动加一。 正是这种归一化处理,php才能够实现关联和非关联的混合 由于push操作的特殊性,索引key在php数组中先后顺序并不是通过下标大小来决定,而是由push的先后决定。 例如 $arr[1] = 2; $arr[2] = 3; 对于double类型的key,Zend HashTable会将他当做索引key处理 7、Php变量 以上所有的变量在底层都是同一种结构 zval
Zval主要由三部分组成: 1、type:指定了变量所述的类型(整数、字符串、数组等) 2、refcount&is_ref:用来实现引用计数(后面具体介绍) 3、value:核心部分,存储了变量的实际数据 u zvalue Zvalue是用来保存一个变量的实际数据。因为要存储多种类型,所以zvalue是一个union,也由此实现了弱类型。 Php变量类型和其实际存储对应关系如下 IS_LONG -> lvalue IS_DOUBLE -> dvalue IS_ARRAY -> ht IS_STRING -> str IS_RESOURCE -> lvalue u 引用计数 引用计数在内存回收、字符串操作等地方使用非常广泛。Php中的变量就是引用计数的典型应用 Zval的引用计数通过成员变量is_ref和ref_count实现,通过引用计数,多个变量可以共享同一份数据。避免频繁拷贝带来的大量消耗 在进行赋值操作时,zend将变量指向相同的zval同时ref_count++,在unset操作时,对应的ref_count-1。只有ref_count减为0时才会真正执行销毁操作 如果是引用赋值,则zend会修改is_ref为1 u 写时拷贝 Php变量通过引用计数实现变量共享数据,那如果改变其中一个变量值呢? 当试图写入一个变量时,Zend若发现该变量指向的zval被多个变量共享,则为其复制一份ref_count为1的zval,并递减原zval的refcount,这个过程称为“zval分离”。可见,只有在有写操作发生时zend才进行拷贝操作,因此也叫copy-on-write(写时拷贝) 对于引用型变量,其要求和非引用型相反,引用赋值的变量间必须是捆绑的,修改一个变量就修改了所有捆绑变量。 整数、浮点数类型变量 对于整数和浮点数,在zvalue中直接存储对应的值。其类型分别是long和double。 从zvalue结构中可以看出,对于整数类型,和c等强类型语言不同,php是不区分int、unsigned int、long、long long等类型的,对它来说,整数只有一种类型也就是long。由此,可以看出,在php里面,整数的取值范围是由编译器位数来决定而不是固定不变的。 对于浮点数,类似整数,它也不区分float和double而是统一只有double一种类型。 在php中,如果整数范围越界了怎么办? 字符串变量 通过zvalue结构可以看出,在php中,字符串是由由指向实际数据的指针和长度结构体组成,这点和c++中的string比较类似。 由于通过一个实际变量表示长度,和c不同,它的字符串可以是2进制数据(包含\0),同时在php中,求字符串长度strlen是O(1)操作。 在新增、修改、追加字符串操作时,php都会重新分配内存生成新的字符串。 最后,出于安全考虑,php在生成一个字符串时末尾仍然会添加\0 常见的字符串拼接方式及速度比较 $strA=‘123’; $strB = ‘456’; $intA=123; intB=456; 现在对如下的几种字符串拼接方式做一个比较和说明 1、$res = $strA.$strB和$res = “$strA$strB” 这种情况下,zend会重新malloc一块内存并进行相应处理,其速度一般 2、$strA = $strA.$strB 这种是速度最快的,zend会在当前strA基础上直接relloc,避免重复拷贝 3、$res = $intA.$intB 这种速度较慢,因为需要做隐式的格式转换,实际编写程序中也应该注意尽量避免 4、$strA = sprintf (“%s%s”,$strA.$strB); 这会是最慢的一种方式,因为sprintf在php中并不是一个语言结构,本身对于格式识别和处理就需要耗费比较多时间,另外本身机制也是malloc。不过sprintf的方式最具可读性,实际中可以根据具体情况灵活选择。 数组变量 foreach操作如何实现? Count操作直接调用HashTable->NumOfElements,O(1)操作 对于’123’这样的字符串,zend会转换为其整数形式。$arr[‘123’]和$arr[123]是等价的 资源类型变量 Php的zval可以表示广泛的数据类型,但是对于自定义的数据类型却很难充分描述。由于没有有效的方式描绘这些复合结构,因此也没有办法对它们使用传统的操作符。要解决这个问题,只需要通过一个本质上任意的标识符(label)引用指针,这种方式被称为资源。 在zval中,对于resource,lval作为指针来使用,直接指向资源所在的地址。 Resource可以是任意的复合结构,我们熟悉的mysqli、fsock、memcached等都是资源。 使用资源 ? 获取一个资源变量 ? 资源销毁 持久化资源 很多情况下,持久化资源可以在一定程度上提高性能。比如我们常见的mysql_pconnect ,持久化资源通过pemalloc分配内存,这样在请求结束的时候不会释放。 对zend来说,对两者本身并不区分。 Php变量的作用域 获取变量值 函数中使用全局变量 转自:搜索研发部官方博客,原文地址:http://stblog.baidu-tech.com/?p=763
|
PHP底层之PHP底层工作原理
最新推荐文章于 2024-07-15 13:14:46 发布