1.字节对齐
1.1 好处:
- 为了减少使用的内存
- 为了提升数据读取的效率
-
#pragma pack(1) :可以让编译器按照1字节对齐
1.2 结构体和联合体对齐:
1.3 替换,在源码里我们经常看见这样的宏,逼格很高
1.4 小而巧的zval
php7和php5不同的地方有很多,zval,zend_value结构就是其中之一。在zval这个结构体重包含三个部分 zend_value(存储实际的内容),u1,u2两个联合体,其中u1主要存储变量相关的一些属性,而u2则是对u1的一些补充,例如当用到数组的时候,会用到u2.next来解决key哈希后出现的hash冲突。
struct _zval_struct {
zend_value value; /* 存储变量的实际内容 */
union {
struct {
ZEND_ENDIAN_LOHI_4(
zend_uchar type, /* 存储变量的类型 */
zend_uchar type_flags, /* 用于标识变量状态,例如GC方面的管理,通过设置为
IS_TYPE_COLLECTABLE 则变量会被收集到GC中回收垃圾的buffer缓存区中 */
zend_uchar const_flags,
zend_uchar reserved) /* call info for EX(This) */
} v;
uint32_t type_info;
} u1;
union {
uint32_t next; /* hash collision chain */
uint32_t cache_slot; /* literal cache slot */
uint32_t lineno; /* line number (for ast nodes) */
uint32_t num_args; /* arguments number for EX(This) */
uint32_t fe_pos; /* foreach position */
uint32_t fe_iter_idx; /* foreach iterator index */
uint32_t access_flags; /* class constant access flags */
uint32_t property_guard; /* single property guard */
uint32_t extra; /* not further specified */
} u2;
};
1.4.1 php7中zval结构:
1.4.2 php7中zend_value结构:
typedef union _zend_value {
zend_long lval; //整形
double dval; //浮点型
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_t w1;
uint32_t w2;
} ww;
} zend_value;
1.4.3 php7中 zend_uchar type的类型:
#define IS_UNDEF 0
#define IS_NULL 1
#define IS_FALSE 2
#define IS_TRUE 3
#define IS_LONG 4
#define IS_DOUBLE 5
#define IS_STRING 6
#define IS_ARRAY 7
#define IS_OBJECT 8
#define IS_RESOURCE 9
#define IS_REFERENCE 10
1.4.4 zend_value各种类型的构造:
1.4.5 php中的COW(copy on write/写时复制)
- 直接复制
$a=10;
$b=$a;
这个操作会使两个zval指向同一个zend_value
这里并没有触发COW,执行深拷贝
当$b = "new string";发生了写操作的时候,触发COW,执行深拷贝,拷贝了完全一样的一份zend_value,$b所在的zval由原来的和$a所在的zval共同指向之前的zend_value, 转换成指向拷贝出的新的zend_value,如果不进行深拷贝的话,那么当执行$b= "new string";后,$a也会等于"new string"。具体过程如下:
1.
2.
3.
4.
1.5 PHP7数组的实现
//Bucket:散列表中存储
typedef struct _Bucket {
zval val; //存储的具体value,这里嵌入了一个zval,而不是一个指针
zend_ulong h; //key根据times 33计算得到的哈希值,或者是数值索引编号
zend_string *key; //存储元素的key
} Bucket;
//HashTable结构
typedef struct _zend_array HashTable;
struct _zend_array {
zend_refcounted_h gc;
union {
struct {
ZEND_ENDIAN_LOHI_4(
zend_uchar flags,
zend_uchar nApplyCount,
zend_uchar nIteratorsCount,
zend_uchar reserve)
} v;
uint32_t flags;
} u;
uint32_t nTableMask; //哈希值计算掩码,等于nTableSize的负值(nTableMask = -nTableSize)
Bucket *arData; //存储元素数组,指向第一个Bucket
uint32_t nNumUsed; //已用Bucket数
uint32_t nNumOfElements; //哈希表有效元素数
uint32_t nTableSize; //哈希表总大小,为2的n次方
uint32_t nInternalPointer;
zend_long nNextFreeElement; 下一个可用的数值索引,如:arr[] = 1;arr["a"] = 2;arr[] = 3;则nNextFreeElement = 2;
dtor_func_t pDestructor;
};
- nNumOfElements
标识现在存储在数组里面的值的数量。这也是函数count的返回值 - nTableSize
表示哈希表的容量。它通常是下一个大于等于nNumOfElements的2的幂值。比如,如果数组存储了32元素,那么哈希表也是32大小的容量。但如果再多一个元素添加进来,也就是说,数组现在有33个元素,那么哈希表的容量就被调整为64。 这是为了保持哈希表在空间和时间上始终有效。很明显,如果哈希表太小,那么将会有很多的冲突,而且性能也会降低。另一方面,如果哈希表太大,那么浪费内存。2的幂值是一个很好的折中方案。 - nTableMask
是哈希表的容量减一。这个mask用来根据当前的表大小调整生成的哈希值。例如,”foo”真正的哈希值(使用DJBX33A哈希函数)是193491849。如果我们现在有64容量的哈希表,我们明显不能使用它作为数组的下标。取而代之的是通过应用哈希表的mask,然后只取哈希表的低位。
hash | 193491849 | 0b1011100010000111001110001001
& mask | & 63 | & 0b0000000000000000000000111111
= index | = 9 | = 0b0000000000000000000000001001 - nNextFreeElement
是下一个可以使用的数字键值,当你使用$array[] = “abc”时候被用到。 - pInternalPointer
存储数组当前的位置。这个值在foreach遍历时可使用reset(),current(),key(),next(),prev()和end()函数访问。 - pListHead和pListTail
标识了数组的第一个和最后一个元素的位置。记住:PHP的数组是有序集合。比如,[‘foo’ => ‘bar’, ‘bar’ => ‘foo’]和[‘bar’ => ‘foo’, ‘foo’ => ‘bar’]这两个数组包含了相同的元素,但却有不同的顺序。 - arBuckets
是我们经常谈论的“哈希表(internal C array)”。它用Bucket **来定义,因此它可以被看作数组的bucket指针(我们会马上谈论Bucket是什么)。 - pDestructor
是值的析构器。如果一个值从HT中移除,那么这个函数会被调用。常见的析构函数是zval_ptr_dtor。zval_ptr_dtor会减少zval的引用数量,而且,如果它遇到o,它会销毁和释放它。
- 数组实现过程1:
- 数组实现过程2:
- 数组实现过程3:
- 数组实现过程4: