PHP7 的高性能来自于哪些改进

  • 变量容器 zval 结构体定义改变,减少内存占用,减少引用计数相关操作。
  • zend_string。字符串复制的时候,采用引用赋值,zend_string可以避免的内存拷贝。
  • zend_array。数组的value默认为zval。
    HashTable的大小从72下降到56字节,减少22%。
    Buckets的大小从72下降到32字节,减少50%。
    数组元素的Buckets的内存空间是一同分配的。
    数组元素的key(Bucket.key)指向zend_string。
    数组元素的value被嵌入到Bucket中。
    降低CPU Cache Miss。
  • 改进函数调用机制。
  • 通过宏定义和内联函数(inline),让编译器提前完成部分工作

参考链接:

  1. http://www.csdn.net/article/2015-09-16/2825720
  2. PHP’s new hashtable implementation
  3. Internal value representation in PHP 7 – Part 2

PHP内存回收原理

每个php变量存在一个叫”zval”的变量容器中。除了包含变量的类型和值,还包括两个字节的额外信息。is_ref 是个bool值,用来标识这个变量是否是属于引用集合(reference set),refcount 用以表示指向这个zval变量容器的变量(也称符号即symbol)个数

refcount 为0时变量从内存中删除。在5.3之前的版本无法处理循环引用的问题。5.3及以后, 在引入新的垃圾回收算法来对付循环引用计数的时候, 作者加入了大量的宏来操作refcount, 为了能让错误更快的显现, 所以改名为refcount__gc, 迫使大家都使用宏来操作refcount。

一个zval在5.3之前版本占用24字节(64位系统,下同),5.3为了解决循环引用的问题,用zval_gc_info劫持了zval的分配,因此5.3到5.6,一个zval实际占用32字节。

从PHP7开始, 对于在zval的value字段中能保存下的值, 就不再对他们进行引用计数了, 而是在拷贝的时候直接赋值, 这样就省掉了大量的引用计数相关的操作, 这部分类型有:IS_LONG、IS_DOUBLE,对于那种根本没有值, 只有类型的类型, 也不需要引用计数了:IS_NULL、IS_FALSE、IS_TRUE。

PHP7的性能,我们并没有引入什么新的技术模式, 不过就是主要来自, 持续不懈的降低内存占用, 提高缓存友好性, 降低执行的指令数的这些原则而来的。

PHP5(v<5.3) zval 结构体定义:

struct _zval_struct {
    union {
        long lval;					/* long value */
        double dval;				/* double value */
        struct {
            char *val;
            int len;
        } str;
        HashTable *ht;				/* hash table value */
        zend_object_value obj;
    } value;
    unsigned int refcount;
    unsigned char type;
    unsigned char is_ref;
};

PHP5(5.3<= v < 7) zval 结构体定义:

struct _zval_struct {
    union {
        long lval;					/* long value */
        double dval;				/* double value */
        struct {
            char *val;
            int len;
        } str;
        HashTable *ht;				/* hash table value */
        zend_object_value obj;
    } value;
    unsigned int refcount__gc;
    unsigned char type;
    unsigned char is_ref__gc;
};

5.3及以上版本虽然 struct  _zval_struct 结构体的内容没有变(除了 refcount 重命名为 refcount__gc,is_ref 重命名为 is_ref__gc),但是新增的头文件 zend_gc.h 重定义了 ALLOC_ZVAL 宏

#undef  ALLOC_ZVAL
#define ALLOC_ZVAL(z) 									\
	do {												\
		(z) = (zval*)emalloc(sizeof(zval_gc_info));		\
		GC_ZVAL_INIT(z);								\
	} while (0)

zval_gc_info 的定义为

typedef struct _zval_gc_info {
	zval z;
	union {
		gc_root_buffer       *buffered;
		struct _zval_gc_info *next;
	} u;
} zval_gc_info;

所以实际上5.3及以上版本zval大小为32字节。

PHP7 zval 结构体定义如下

struct _zval_struct {
    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;            /* value */
    union {
        struct {
            ZEND_ENDIAN_LOHI_4(
                    zend_uchar type,            /* active type */
                    zend_uchar type_flags,
                    zend_uchar const_flags,
                    zend_uchar reserved)        /* call info for EX(This) */
        } v;
        uint32_t type_info;
    } u1;
    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;
};

 

关于内存对齐请参考:https://levphy.github.io/2017/03/23/memory-alignment.html