深入了解php底层机制

网络整理 - 08-01

   作为一门动态语言,和php

  语法简单灵活,没有太多规范。(导致风格混杂)

  再差的程序员也不会写出太离谱危害全局的程序。

  3、Php的四层体系Php的核心架构如下图

Zval主要由三部分组成:1、 type:指定了变量所述的类型(整数、字符串、数组等)2、 refcount&is_ref:用来实现引用计数(后面具体介绍)3、 value:核心部分,存储了变量的实际数据u zvalueZvalue是用来保存一个变量的实际数据。因为要存储多种类型,所以zvalue是一个union,也由此实现了弱类型。Php变量类型和其实际存储对应关系如下IS_LONG -> lvalueIS_DOUBLE -> dvalueIS_ARRAY -> htIS_STRING -> strIS_RESOURCE -> lvalueu 引用计数引用计数在内存回收、字符串操作等地方使用非常广泛。Php中的变量就是引用计数的典型应用Zval的引用计数通过成员变量is_ref和ref_count实现,通过引用计数,多个变量可以共享同一份数据。避免频繁拷贝带来的大量消耗在进行赋值操作时,zend将变量指向相同的zval同时ref_count++,在unset操作时,对应的ref_count-1。只有ref_count减为0时才会真正执行销毁操作如果是引用赋值,则zend会修改is_ref为1u 写时拷贝Php变量通过引用计数实现变量共享数据,那如果改变其中一个变量值呢?当试图写入一个变量时,Zend若发现该变量指向的zval被多个变量共享,则为其复制一份ref_count为1的zval,并递减原zval的refcount,这个过程称为“zval分离”。可见,只有在有写操作发生时zend才进行拷贝操作,因此也叫copy-on-write(写时拷贝)对于引用型变量,其要求和非引用型相反,引用赋值的变量间必须是捆绑的,修改一个变量就修改了所有捆绑变量。整数、浮点数类型变量整数、浮点数是php中的基础类型之一,也是一个简单型变量。对于整数和浮点数,在zvalue中直接存储对应的值。其类型分别是long和double。从zvalue结构中可以看出,对于整数类型,和c等强类型语言不同,php是不区分int、unsigned int、long、long long等类型的,对它来说,整数只有一种类型也就是long。由此,可以看出,在php里面,整数的取值范围是由编译器位数来决定而不是固定不变的。对于浮点数,类似整数,它也不区分float和double而是统一只有double一种类型。在php中,如果整数范围越界了怎么办?

  这种情况下会自动转换为double类型,这个一定要小心,很多trick都是由此产生。字符串变量和整数一样,字符变量也是php中的基础类型和简单型变量通过zvalue结构可以看出,在php中,字符串是由由指向实际数据的指针和长度结构体组成,这点和c++中的string比较类似。由于通过一个实际变量表示长度,和c不同,它的字符串可以是2进制数据(包含\0),同时在php中,求字符串长度strlen是O(1)操作。在新增、修改、追加字符串操作时,php都会重新分配内存生成新的字符串。最后,出于安全考虑,php在生成一个字符串时末尾仍然会添加\0常见的字符串拼接方式及速度比较

  假设有如下4个变量:$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的方式最具可读性,实际中可以根据具体情况灵活选择。数组变量如前所述,Php的数组通过Zend HashTable来天然实现foreach操作如何实现?

  对一个数组的foreach就是通过遍历hashtable中的双向链表完成。对于索引数组,通过foreach遍历效率比for高很多,省去了key->value的查找Count操作直接调用HashTable->NumOfElements,O(1)操作对于’123’这样的字符串,zend会转换为其整数形式。$arr[‘123’]和$arr[123]是等价的资源类型变量 这是php中最复杂的一种变量,也是一种复合型结构。Php的zval可以表示广泛的数据类型,但是对于自定义的数据类型却很难充分描述。由于没有有效的方式描绘这些复合结构,因此也没有办法对它们使用传统的操作符。要解决这个问题,只需要通过一个本质上任意的标识符(label)引用指针,这种方式被称为资源。在zval中,对于resource,lval作为指针来使用,直接指向资源所在的地址。Resource可以是任意的复合结构,我们熟悉的mysqli、fsock、memcached等都是资源。使用资源

  • 注册

  对于一个自定义的数据类型,要想将它作为资源。首先需要进行注册,zend会为它分配全局唯一标示• 获取一个资源变量

  对于资源,zend维护了一个id->实际数据的hash_tale。对于一个resource,在zval中只记录了它的id。fetch的时候通过id在hash_table中找到具体的值返回• 资源销毁

  资源的数据类型是多种多样的。Zend本身没有办法销毁它。因此需要用户在注册资源的时候提供销毁函数。当unset资源时,zend调用相应的函数完成析构。同时从全局资源表中删除它。持久化资源

  资源可以长期驻留,不只是在所有引用它的变量超出作用域之后,甚至是在一个请求结束了并且新的请求产生之后。这些资源称为持久资源,因为它们贯通SAPI的整个生命周期持续存在,除非特意销毁。很多情况下,持久化资源可以在一定程度上提高性能。比如我们常见的mysql_pconnect ,持久化资源通过pemalloc分配内存,这样在请求结束的时候不会释放。对zend来说,对两者本身并不区分。Php变量的作用域Php中的局部变量和全局变量是如何实现的?

  对于一个请求,任意时刻php都可以看到两个符号表(symbol_table和active_symbol_table),其中前者用来维护全局变量。后者是一个指针,指向当前活动的变量符号表,当程序进入到某个函数中时,zend就会为它分配一个符号表x同时将active_symbol_table指向a。通过这样的方式实现全局、局部变量的区分获取变量值

  php的符号表是通过hash_table实现的,对于每个变量都分配唯一标识,获取的时候根据标识从表中找到相应zval返回函数中使用全局变量

  在函数中,我们可以通过显式申明global来使用全局变量。在active_symbol_table中创建symbol_table中同名变量的引用,如果symbol_table中没有同名变量则会先创建。