PHP变量类型底层设计实现

Posted jfcat

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PHP变量类型底层设计实现相关的知识,希望对你有一定的参考价值。

基础知识

每个类型的字节长度(x86-64体系)

int 4字节
char 1字节
long 8字节
pointer 8字节

字节对齐

struct test{
	char a;
	int b;
	long c;
	void *d;
	int e;
	char *f;
};

如果单独把每个类型的字节数相加是33字节,但是实际上要使用40字节

内存对齐的本质是cpu对程序内存数据获取的要求,这样可以加快数据IO的过程,避免不必要的计算工作。

联合

uniontest{
	char a;
	int b;
	long c;
};

联合体只共享内存,只使用最大的成员的内存大小
例如:定义一个如下c文件

union data {
    u_int32_t a;
    u_int32_t b;
    u_int32_t c;
} d;

int main() {
    d.a = 30;
    printf("a %d b %d c %d\\n", d.a, d.b, d.c);

    d.b = 10;
    printf("a %d b %d c %d\\n", d.a, d.b, d.c);
}

编译执行后得到的结果是,

$ gcc union.c
$ ./a.out 
a 30 b 30 c 30
a 10 b 10 c 10

有了基本认知我们再来看看zval的设计

PHP5的zval设计

PHP5中_zval_struct(zval)结构

struct _zval_struct{
	zvalue_value value; /*value值*/
	zend_uint refcount__gc; /*类型*/
	zend_uchar type; 
	zend_uchar is_ref__gc;
};
typedef union_zvalue _value {
	long lval; /*长整型*/
	double dval; /*浮点型*/
	struct{
	char *val;
	int len;
	} str;
	HashTable *ht;/*HashTable数*/
	zend_object_value obj;
	zend_ast *ast;
} zvalue_value;

通过联合结构很容易看出来,php的整形值实际上是长整型,浮点数是double,字符串是一个结构体,表示byte数组和长度;剩下部分就是数组和对象;
zval结构所表示的是一个zvalue_value和引用计数,以及变量类型和是否引用类型等。

使用zend内存池后zval结构的变化

typedef struct _zend_mm_block_info {
	size_t _size;
	size_t _prev;
} zend_mm_block_info;
typedef struct _zend_mm_block {
	zend_mm_block_infoinfo;
} zend_mm_block;

最终一个变量在PHP5中实际占用的内存大小为48字节

这48字节的大小其实有很多的浪费,而这点PHP开发者在PHP7中做了重点优化。

  • 题外话:PHP5.3解决循环引用
    通过重写分配zval的宏,对zval进行扩充
#undef ALLOC_ZVAL
#define ALLOC_ZVAL(z)\\
	do{ \\
	(z)=(zval*)emalloc(sizeof(zval_gc_info));\\
	 GC_ZVAL_INIT(z);
	} while0typedef struct _zval_gc_info{
	zval z;
	union {
		gc_root_buffer* buffered;
		struct _zval_gc_info *next;
	} u;
} zval_gc_info;

从结构体看,猜测的使用的方法是通过对zval结构循环记录来判断是否变量存在循环引用,相当于对有环链表的循环查找。

PHP7的Zval设计

struct _zval_struct {
zend_value value;
union {
	struct {
		ZEND_ENDIAN_LOHI_4(
			zend_uchar type,/*标明zval类型*/
			zend_uchar type_flags,
			zend_uchar const_flags,
			zend_uchar reserved)
		} v;
	uint32_t type_info;
	} u1;
	union {
		uint32_t next; /*用来解决哈希冲突,详见第5章*/
		uint32_t cache_slot; /*运行时缓存*/
		uint32_t lineno;  /*对于zend_ast_zval存行号*/
		uint32_t num_args;/*EX(This)参数个数*/
		uint32_t fe_pos; /*foreach的位置*/
		uint32_t fe_iter_idx; /*foreach游标的标记*/
		uint32_t access_flags; /*类的常量访问标识*/
		uint32_t property_guard; /*单一属性保护*/
	} u2;
};


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;/*zval类型*/
	void *ptr;/*指针类型*/
	zend_class_entry *ce;/*class类型*/
	zend_function *func;/*function类型*/
	struct{
		uint32_t w1;
		uint32_t w2;
	} ww;
} zend_value;
 

zval结构的主要变化时多了两个union结构,我们主要看下这个变化。

u1字段的含义

  • type:记录变量类型。
  • type_flag: 对应变量类型特有的标记,不同类型的变量对应的flag也不同
/*zval.u1.v.type_flags*/
IS_TYPE_CONSTANT//是常量类型
IS_TYPE_IMMUTABLE//不可变的类型,比如存在共享内存中的数组
IS_TYPE_REFCOUNTED//需要引用计数的类型
IS_TYPE_COLLECTABLE//可能包含循环引用的类型(IS_ARRAY,IS_OBJECT)
IS_TYPE_COPYABLE//可被复制的类型

  • const_flag:常量类型的标记,对应的属性有:
/*zval.u1.v.const_flags*/
#define IS_CONSTANT_UNQUALIFIED 0x010
#define IS_CONSTANT_VISITED_MARK 0x020
#define IS_CONSTANT_CLASS0 x080 /*__CLASS__trail类*/
#define IS_CONSTANT_IN_NAMESPACE 0x100/*只用在opline>extended_value*/
  • reserved:保留字段。

u2中的字段信息

  • next:用来解决哈希冲突问题,记录冲突的下一个元素位置
  • cache_slot:运行时缓存。在执行函数时会优先去缓存中查找,若缓存中没有,会在全局的function表中查找。
  • lineno:文件执行的行号,应用在AST节点上
  • num_args:函数调用时传入参数的个数
  • fe_pos:遍历数组时的当前位置
  • fe_iter_idx:跟fe_pos用途类似,只是这个字段是针对对象的。
  • access_flags:对象类的访问标志,常用的标识有public、protected、private。
  • property_guard:防止类中魔术方法的循环调用,

PHP7的zval的内存占用情况


对比PHP5的内存占用48个字节,PHP7的zval内存占用要小很多。

参考:
https://book.douban.com/subject/30455287/

以上是关于PHP变量类型底层设计实现的主要内容,如果未能解决你的问题,请参考以下文章

PHP变量和数据类型

go——切片

并发编程艺术-锁类型以及底层原理

PHP底层的运行机制与原理

springboot 底层点的知识

springboot 底层点的知识