2015年,PHP7的發佈能夠說是在技術圈裏引發了不小的轟動,由於它的執行效率比PHP5直接翻了一倍。PHP7在內存方面,你是否知道做者都進行了哪些優化?幾個核心結構體的改進只是表面上看起來優化的幾個字節那麼簡單?讓咱們從幾個核心的數據結構改進開始看起。php
1PHP7 zval的變化一、php5.3中的zval:緩存
typedef unsigned int zend_object_handle;
typedef struct _zend_object_value {
zend_object_handle handle;
zend_object_handlers *handlers;
} zend_object_value;
typedef union _zvalue_value {
long lval; /* long value */
double dval; /* double value */
struct {
char *val;
int len;
} str;
HashTable *ht; /* hash table value */
zend_object_value obj;
} zvalue_value;
struct _zval_struct {
/* Variable information */
zvalue_value value; /* value */
zend_uint refcount__gc;
zend_uchar type; /* active type */
zend_uchar is_ref__gc;
};
咱們這裏只討論64位操做系統下的狀況。該zval_struct結構體中的由四個成員構成,其中zvalue_value稍微複雜一些,是一個聯合體。聯合體中最長的成員是一個指針加一個int,8+4=12字節。可是默認狀況下,會進行內存對齊,故zval_struct會佔用16字節。 那麼。數據結構
_zval_struct總的字節 = value(16)+ refcount__gc(4)+ type(1)+ is_ref__gc(1)= 佔用22字節。app
最後再考慮下內存對齊,實際佔用24字節。(若是算的有點暈話,感興趣的同窗能夠寫段簡單的測試代碼,使用sizeof查看一下)ide
二、PHP7.2中的zval性能
typedef struct _zval_struct zval;
typedef union _zend_value {
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;
} zend_value;
struct _zval_struct {
zend_value value; /* value */
union {
struct {
ZEND_ENDIAN_LOHI_4(
zend_uchar type,
zend_uchar type_flags,
zend_uchar const_flags,
zend_uchar reserved)
} v;
int type_info;
} u1;
union { ...... } u2;
};
7.2中的zval_struct結構體裏由3個成員構成,其中zend_value看起來比較複雜,實際上只是一個8字節的聯合體。 u1也是一個聯合體,佔用是4個字節。u2也同樣。這樣zval_struct就實際佔用16個字節。測試
2PHP7 HashTable的變化一、PHP5.3裏的HashTable:
優化
typedef struct _hashtable {
uint nTableSize;
uint nTableMask;
uint nNumOfElements; //注意這裏:浪費ing
ulong nNextFreeElement;
Bucket *pInternalPointer; /* Used for element traversal */
Bucket *pListHead;
Bucket *pListTail;
Bucket **arBuckets;
dtor_func_t pDestructor;
zend_bool persistent;
unsigned char nApplyCount;
zend_bool bApplyProtection;
} HashTable;
在5.3裏HashTable就是一個大struct, 有點小複雜,咱們拆開了細說,ui
uint nTableMask 4字節spa
uint nNumOfElements 4字節,
ulong nNextFreeElement 8字節 注意這前面的4個字節會被浪費掉,由於nNextFreeElement的開始地址須要對齊
Bucket *pInternalPointer 8字節
Bucket *pListHead 8字節
Bucket *pListTail 8字節
Bucket **arBuckets 8字節
dtor_func_t pDestructor 8字節
zend_bool persistent 1字節
unsigned char nApplyCoun 1字節
zend_bool bApplyProtection 1字節
最終,總字節數 = 4+4+4+4(nNextFreeElement前面這四個字節會留空)+8+8+8+8+8+8+1+1+1 = 67字節。再加上結構體自己要對齊到8的整數倍,因此實際佔用72字節。
二、PHP7.2裏的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 consistency)
} v;
uint32_t flags;
} u;
uint32_t nTableMask;
Bucket *arData;
uint32_t nNumUsed;
uint32_t nNumOfElements;
uint32_t nTableSize;
uint32_t nInternalPointer;
zend_long nNextFreeElement;
dtor_func_t pDestructor;
};s
在7.2裏HashTable
union... u 佔用4字節
uint32_t 佔用4字節
Bucket* 指針佔用8字節
uint32_t nNumUsed 佔用4字節
uint32_t nNumOfElements 佔用4字節
uint32_t nTableSize 佔用4字節
uint32_t nInternalPointer 佔用4字節
zend_long nNextFreeElement 佔用8字節
dtor_func_t pDestructor 佔用8字節
總佔用字節數 = 8+4+4+8+4+4+4+4+8+8 = 56字節,而且正好達到了內存對齊的狀態,沒有額外的浪費。
另外還有PHP源代碼裏常常出鏡的Buckets也從72降低到了32字節,這裏我就不翻源代碼了。
3優化思想精髓噹噹噹,敲黑板,重點來了!咱們看了兩個核心數據結構的結構體變化,這上面的優化都是什麼含義呢? 拿HashTable舉例,貌似從72字節優化到了56字節,這內存節約的也不是特別多嘛,才20%多而已!但這中間其實隱藏了兩個較深層次優化思路:
第1、你是否記得咱們前面CPU在向內存要數據的時候是以Cache Line爲單位進行的,而咱們說過Cache Line的大小就是64字節。回過頭來看HashTable,在7.2裏的56字節,只須要CPU向內存進行一次Cache Line大小的burst IO,就夠了。而在5.3裏的72字節,雖然只比Cache Line大了那麼一丟丟,可是對不起,必須得進行兩次burst IO才能夠。 因此,在計算機裏,56字節相對72字節其實是翻倍的性能提高!!
第2、CPU的L一、L二、L3的容量是固定的幾十K或者幾十M。假設Cache的都是HashTable,那麼Cache容量不變的條件下,能Cache住的HashTable將會翻倍,緩存命中率提高一大截。要知道L1命中後只須要1ns多一點的耗時,而若是穿透到內存的話可能就須要40多納秒的延時了,整整差了幾十倍。
因此PHP內核的做者大牛深諳CPU與內存的工做原理,表面上看起來只是幾個字節的節約,可是實際上爆發出了巨大的性能提高!!