为什么php7比php5性能提升那么多,都做了什么优化?
1.变量->zval->结构改变
(1)变小了
(2)结构体中共用内存,减少引用.内存降低,引用调用减少速度变快
2.怎么变的
(1)php5中 一共24字节(64位)
struct _zval_struct{ union{ }value; zend_uchar type; }
用一个_zval_struct的结构体表示变量,因为是弱类型,有一个type,和具体的value(联合体) ,通过不同的函数去解析value,实现不同的效果
给变量添加新能力:垃圾回收(自动回收机制),需要引用计数refcount__gc;
struct _zval_struct{ union{ }value; zend_uchar type; zend_uint refcount__gc; }
再添加新能力:引用传递,需要标记变量是一个引用is_ref__gc;
struct _zval_struct{ union{ }value; zend_uchar type; zend_uint refcount__gc; zend_uchar is_ref__gc; }
实际的变量zval结构体长成这样
struct _zval_struct{ union{ long lval; doublie dval; struct{ char *val; int len; }str; HashTable *ht; zend_object_value obj; zend_ast *ast; }value; zend_uchar type; zend_uint refcount__gc; zend_uchar is_ref__gc; }
(2)现有问题
a.没有预留新字段的位置,新添加功能要不外部链接,要不内部调用
b.因为a,浪费内存.只能定义独立的外部对象,和特殊的处理方法.只对对象,资源回收需要特殊的info结构体特殊处理.需要引用计数,原来是24k现在是32k,也就是一个进程使用32k的大小
c.对象,资源是引用传递,需要维护一个全局的引用信息
d.当别的地方要用string类型的val时只能复制
e.写时分离,一个数组取引用后自己变成引用,传给一个普通参数函数,需要复制当前这个引用对应的数据
function t($arr) { } //第一种没有复制 $arr = [1,2,3]; t($arr); //第二种复制了一个新的 $b = &$arr; t($arr);
f.操作一个变量的过程,
(1)在堆上分配内存a,构建变量,MAKE_STD_ZVAL/ALLOC_ZVAL
(2)进行函数运算,修改变量
(3)返回变量,RETURN_ZVAL,这时会从内存中copy数据到需要的地方
(4)销毁申请的内存a
这块儿内存a起到一个临时数据的作用,是可以用栈来实现(栈快呀)
(3).php7 zval 16字节(64位)
分3块儿:value,u1,u2
value是具体值
u1类型信息
u2辅助字段
struct _zval_struct{ union{ }value; union{ }u1; union{ }u2; }
value保持一个指针,一个double,或者一个long等,表示这个变量的值
- union {
- zend_long lval; /* long value */
- double dval; /* double value */
- 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;
- } value;
u1保存了变量的类型
union{ struct{ ZEND_ENDIAN_LOHI_4( zend_uchar type, zend_uchar type_flags, zend_uchar const_flags, zend_uchar reserved ) }v; uint32_t type_info; }u1;
u2是扩充辅助字段
- union {
- uint32_t var_flags;
- 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 */
- } u2;
3.解决问题
1.引用计数
(1)明确一共有多少类型,有些是不需要引用计数的
#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 /* constant expressions */ #define IS_CONSTANT 11 #define IS_CONSTANT_AST 12 /* fake types */ #define _IS_BOOL 13 #define IS_CALLABLE 14 /* internal types */ #define IS_INDIRECT 15 #define IS_PTR 17
a.对于is_long,is_double不需要引用计数,直接copy赋值,用完销毁
b.只有类型没有值得也不需要引用计数,is_null,is_true,is_false
c.复杂类型,一个size_t保存不了,使用指针表示那个值,而引用计数维护在这个值本身上
举例数组: zval->_zend_array->zend_refcounted_h gc->refcount
这个gc是一个维护在_zend_array上的结构体.
2.插播广告
(1)php5 -> php7 is_bool 拆分为is_true,is_false
(2)php5 -> php7 is_ref变为一个引用类型,原来是通过一个标识位标识
(3)php5-> php7 所有复杂类型引用计数refcount__gc 从一个数字变为一个类型zend_refcounted_h,统一了行为方式,同时还包含一些gc信息
struct _zend_array{ zend_refcounted_h gc; } typedef struct _zend_refcounted_h{ uint32_t refcount; }zend_refcounted_h;
(4).ZEND_ENDIAN_LOHI_4它会保证在大端或者小端的机器上, 它定义的字段都按照一样顺序排列存储, 从而我们在赋值的时候, 不需要对它的字段分别赋值, 而是可以统一赋值
(5),type为什么不在value前面,如果在前面先获取type再获取value,快.因为jit以后value类型是通过推导得到,那么不需要获取type.
3.添加标记位type_flags
zval.u1.v.type_flags标记了type下的详细分类
4.内存分配
原来是24+gc_info8+php内存管理的一些字节=48
php的zval可能是符号表,临时变量,编译变量