PHP变量类型底层设计实现
Posted jfcat
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PHP变量类型底层设计实现相关的知识,希望对你有一定的参考价值。
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);
} while(0)
typedef 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变量类型底层设计实现的主要内容,如果未能解决你的问题,请参考以下文章