baiyan所有视频:https://segmentfault.com/a/11...源视频地址:http://replay.xesv5.com/ll/24...简介和基本概念变量本质上是给一段内存空间起一个名字。如果我们要基于C语言设计一个存储$a=1等变量的数据结构,应该如何设计呢?变量的基本元素是类型和值,有些类型还有其他描述字段(如长度等)。首先,应该定义一个结构作为基本数据结构。第一个问题:如何存储变量类型?答:使用unsignedchar类型的字段就可以了,因为unsignedchar类型最多可以表示2^8=256种类型。所有的变量在PHP7中都是用zval表示的,是一个结构体。先看下zval的基本结构:typedefunsignedcharzend_uchar;struct_zval_struct{zend_value值;/*存储变量zhi*/union{struct{ZEND_ENDIAN_LOHI_4(//大小端问题,详见《PHP内存管理3笔记》zend_uchar类型,//注意这里存放的是变量类型,char类型zend_uchartype_flags,//类型标志zend_ucharconst_flags,//是否为常量zend_ucharreserved)//保留字段}v;uint32_t类型信息;}u1;union{uint32_t下一个;/*Array模拟链表,使用*/uint32_tcache_slot;/*文字缓存槽*/uint32_tlineno;/*行号(对于ast节点)*/uint32_tnum_args;EX(这个)*/uint32_tfe_pos;/*foreach位置*/uint32_tfe_iter_idx;/*foreach迭代器索引*/uint32_taccess_flags;/*类常量访问标志*/uint32_tproperty_guard;/*单个属性守卫*/uint32_textra;/*没有进一步说明*/}u2;};注意中文注释部分,PHP使用C语言的unsignedchar类型,存储所有变量的类型在PHP中,所有变量的类型如下:/*常规数据类型*/#defineIS_UNDEF0#defineIS_NULL1#defineIS_FALSE2#defineIS_TRUE3#defineIS_LONG4#defineIS_DOUBLE5#defineIS_STRING6#defineIS_ARRAY7#defineIS_OBJECT8#defineIS_RESOURCE9#defineIS_REFERENCE10/*常量表达式*/#defineIS_CONSTANT11#defineIS_CONSTANT_AST12/*假类型*/#define_IS_BOOL13#defineIS_CALLABLE14#defineIS_ITERABLE19#defineIS_VOID18/*内部类型*/#defineIS_INDIRECT15#defineIS_PTR17#define_IS_ERROR20第二个问题:如何存储变量的值?答:如果a是1,就用int;如果是1.1,则使用double;如果是'1',就用char*等,但是变量值只有一种类型,这是不可能的同时,使用多种类型来存储值,所以我们可以把很多东西放在一个union中。源代码中存储变量类型的联合称为zend_value:typedefunion_zend_value{zend_longlval;//保存整个类型值doubledval;//存储浮点值zend_refcounted*counted;//存储引用计数值zend_string*str;//存储字符串值zend_array*arr;//存储数组值zend_object*obj;//存储对象值zend_resource*res;//保存资源值zend_reference*ref;//保存参考值zend_ast_ref*ast;//保存抽象语法树zval*zv;//内部使用void*ptr;//不确定类型,取出来强行zend_class_entry*ce;//存储类zend_function*func;//存储函数struct{uint32_tw1;uint32_tw2;}ww;//这个union一共有8B,这个结构体的每个字段都是4B,因为所有的union字段共享一块内存,所以相当于拿了一半的union}zend_value;因为有些类型的变量需要一些额外的描述信息(比如字符串,数组),复杂度较高,为了节省空间,只在zend_value结构体中存放了一个结构体指针,其真正的值在这样的结构体中作为zend_string和zend_array(如下所述)。zend_value类型是一个union,一共占用8B。因为变量只有一种类型,所以可以利用union共享一块内存的特性来存储变量的类型。请注意,最后一个结构是一个技巧。通过取ww结构体的其中一个字段,就可以得到union变量的高4位或者低4位,不需要手动写冗余代码来获取。在PHP7中,zend_value占用8B,而u1占用4B,u2占用4B。内存对齐后,一个zval占用16B。与PHP5相比,占用内存大大减少。使用gdb查看变量底层存储示例代码:=0;$i--){$arr2[$i]=$i;}思考:这两段代码占用的内存大小一样吗?答案:第一个for循环占用的内存较少。那么为什么会这样呢?先看两个概念:packedarray和hasharraypackedarray的特点:key是一个数字,顺序递增位置固定。如果访问key为0的元素,即$arr1[0],则直接访问bucket数组的第0位即可(即arData[0]),因为可以直接访问,有不需要在它前面使用额外的索引数组。在PHP中,仅使用2个数组元素并将其分配给初始-1。可以看出,第一个循环是packed以数组的形式存储的。由于没有使用索引数组,索引节省了200,000-2个内存单元。如果不满足以上条件,则为哈希数组。我们来看第二个for循环。如果要访问key为200000的元素,如果按照打包数组的方式,直接访问bucket数组的第200000个元素(即arData[200000]),会得到错误的值0(因为$arr2[200000]=200000),所以只能使用索引数组间接访问元素,因为索引数组需要索引到桶数组的所有位置,所以它的大小等于桶数组的大小,200000-2more由于使用了内存单元,因此占用的内存较多,所以在工作中,尽量使用packedarray来减少对服务器内存的使用量。思考:如果一个packed数组中间有很多元素,即:$arr=[0=>0,1=>1,2=>2,100000=>3];很明显,如果使用packedarray,会浪费很多bucketarrays,这种情况下,使用hasharray的效率可能会更高。在这里,PHP内核需要做出取舍。经过比较,选择最优方案。
