前面我們討論了記憶體的作業原理,也進行了一些性能相關的測驗,那么今天開始我們來看幾個在實踐中的應用,首先我們先從PHP開始,
2015年,PHP7的發布可以說是在技術圈里引起了不小的轟動,因為它的執行效率比PHP5直接翻了一倍,PHP7在記憶體方面,你是否知道作者都進行了哪些優化?你是否能夠深層次理解到作者優化思路的精髓?
讓我們從幾個核心的資料結構改進開始看起,
PHP7 zval變化
1、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位元組,
最后再考慮下記憶體對齊,實際占用24位元組,(如果算的有點暈話,感興趣的同學可以寫段簡單的測驗代碼,使用sizeof查看一下)
2、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個位元組,
PHP7 HashTable變化
1、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, 有點小復雜,我們拆開了細說,
- uint nTableSize 4位元組
- uint nTableMask 4位元組
- 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位元組,
2、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
- zend_refcounted_h gc 看起來唬人,實際就是個long,占用8位元組
- 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位元組
占用56位元組,并且正好達到了記憶體對齊的狀態,沒有額外的浪費,
另外還有PHP源代碼里經常出鏡的Buckets也從72下降到了32位元組,這里我就不翻源代碼了,
優化思路精髓
我們看了兩個核心資料結構的結構體變化,這上面的優化都是什么含義呢? 拿HashTable舉例,貌似從72位元組優化到了56位元組,這記憶體節約的也不是特別多嘛,才20%多而已!
但這中間其實隱藏了兩個較深層次優化思路
第一、CPU在向記憶體要資料的時候是以Cache Line為單位進行的,而我們說過Cache Line的大小就是64位元組,回過頭來看HashTable,在7.2里的56位元組,只需要CPU向記憶體進行一次Cache Line大小的burst IO,就夠了,而在5.3里的72位元組,雖然只比Cache Line大了那么一丟丟,但是對不起,必須得進行兩次burst IO才可以, 所以,在計算機里,72位元組相對56位元組實際上是翻倍的性能提升!!
第二、CPU的L1、L2、L3的容量是固定的幾十K或者幾十M,假設Cache的都是HashTable,那么Cache容量不變的條件下,PHP7里能Cache住的HashTable數量將會翻倍,快取命中率提升一大截,要知道L1命中后只需要1ns多一點的耗時,而如果穿透到記憶體的話可能就需要40多納秒的延時了,整整差了幾十倍,
所以PHP內核的作者大牛深諳CPU與記憶體的作業原理,表面上看起來只是幾個位元組的節約,但是實際上爆發出了巨大的性能提升!!

開發內功修煉之記憶體篇專輯:
- 1.帶你深入理解記憶體對齊最底層原理
- 2.記憶體隨機也比順序訪問慢,帶你深入理解記憶體IO程序
- 3.從DDR到DDR4,記憶體核心頻率其實基本上就沒太大的進步
- 4.實際測驗記憶體在順序IO和隨機IO時的訪問延時差異
- 5.揭穿記憶體廠家“謊言”,實測記憶體帶寬真實表現
- 6.NUMA架構下的記憶體訪問延遲區別!
- 7.PHP7記憶體性能優化的思想精髓
- 8.一次記憶體性能提升的專案實踐
- 9.挑戰Redis單實體記憶體最大極限,“遭遇”NUMA陷阱!
我的公眾號是「開發內功修煉」,在這里我不是單純介紹技術理論,也不只介紹實踐經驗,而是把理論與實踐結合起來,用實踐加深對理論的理解、用理論提高你的技術實踐能力,歡迎你來關注我的公眾號,也請分享給你的好友~~~
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/200871.html
標籤:其他
上一篇:一文搞懂什么是單代號網路圖!
下一篇:一次記憶體性能提升的專案實踐
