介紹 PHP7 HashTable


PHP 數組的底層實現使用了 HashTable 這種數據結構,PHP 7.0 相比於舊版本 PHP 數組做了很多的修改,本文主要記錄 PHP7.0 相對於舊版本修改了那些東西。

介紹 PHP7 HashTable

HasahTable 又叫做散列表,具有如下特點(具體可參考《數據結構與算法分析》第五章散列)

  • 可以以 O(1) 的效率執行數據插入、刪除和查找操作
  • 通過散列函數維護 K-V 之間的映射關系,可能會產生哈希沖突現象等
  • 數據是 K-V 形式存儲,元素間是無順序的結構,不能對數據進行排序

PHP7 優化介紹

PHP 5 到 PHP 7.0 HashTable 主要做了如下修改

  • 優化 HashTable 的數據結構:一個 PHP 數組 zend_array 的內存占用從PHP5點72個字節,降低到了56個字節
  • 使用內存緩存局部性的特點,優化 PHP 數組效率
    • PHP 5 的 Bucket ,包括 zval 都是獨立分配(在內存中是離散的)
    • PHP 7 中使用一塊連續的空間存儲  Bucket,並且一個 Bucket 中包含一個 zval 數據,即 Bucket 和 zval 在內存中都是連續存儲的
  • 其他一些細節優化
    • 指針訪問速度
    • 優化 Empty Array
    • .......

PHP 5 HashTable 的數據結構

typedef struct _hashtable {
    uint nTableSize;        /* 散列表大小, Hash值的區間 */
    uint nTableMask;        /* 等於nTableSize -1, 用於快速定位 */
    uint nNumOfElements;    /* HashTable中實際元素的個數 */
    ulong nNextFreeElement; /* 下個空閑可用位置的數字索引 */
    Bucket *pInternalPointer;   /* 內部位置指針, 會被reset, current這些遍歷函數使用 */
    Bucket *pListHead;      /* 頭元素, 用於線性遍歷 */
    Bucket *pListTail;      /* 尾元素, 用於線性遍歷 */
    Bucket **arBuckets;     /* 實際的存儲容器 */
    dtor_func_t pDestructor;/* 元素的析構函數(指針) */
    zend_bool persistent;
    unsigned char nApplyCount; /* 循環遍歷保護 */
    zend_bool bApplyProtection;
    #if ZEND_DEBUG
    int inconsistent;
    #endif
} HashTable;

typedef struct bucket {
    ulong h;                        /* 數字索引/hash值 */
    uint nKeyLength;                /* 字符索引的長度 */
    void *pData;                    /* 數據 */
    void *pDataPtr;                 /* 數據指針 */
    struct bucket *pListNext;               /* 下一個元素, 用於線性遍歷 */
    struct bucket *pListLast;       /* 上一個元素, 用於線性遍歷 */
    struct bucket *pNext;                   /* 處於同一個拉鏈中的下一個元素 */
    struct bucket *pLast;                   /* 處於同一拉鏈中的上一個元素 */
    char arKey[1]; /* 節省內存,方便初始化的技巧 */
} Bucket;
PHP 5 HashTable 數據結構

從數據結構和下圖可以看出,PHP5 的 HashTable 使用了雙向鏈表實現可以順序遍歷的結構。並使用拉鏈法解決 Hash 沖突

其中需要介紹的字段有:

  • arBuckets 中的 pListHead、pListTail 雙向鏈表的頭和尾指針
  • Bucket 中的 pListNext、pListLast 雙向鏈表的上一個和下一個節點
  • Bucket 中的 pNext、pLast ,拉鏈法解決 Hash 沖突的上一個和下一個這指針
  • 其中 Bucket 代表 HashTable 中的一個鍵值,Bucket->pData 為 zval 存儲值的數據結構

 

PHP7 Hashtable 的數據結構

struct _zend_array {
    zend_refcounted_h gc;
    union {
        struct {
            ZEND_ENDIAN_LOHI_4(
                zend_uchar    flags,
                zend_uchar    _unused,
                zend_uchar    nIteratorsCount,
                zend_uchar    _unused2)
        } 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;
};

typedef struct _Bucket {
    zval              val;
    zend_ulong        h;   /* hash value (or numeric index)   */
    zend_string      *key; /* string key or NULL for numerics */
} Bucket
PHP7 Hashtable 的數據結構

從數據結構和下圖可以看出,arData 和 Bucket 的數據結構都更加的簡潔了。羅列下大概做了如下修改

  • 沖突拉鏈被bauck.zval->u2.next替代, 於是bucket->pNext, bucket->pLast可以去掉了
  • zend_array->arData是一個數組,所以也就不再需要pListNext, pListLast來保持順序了, 他們也可以去掉了。 現在數組中元素的先后順序,完全根據它在arData中的index順序決定,先加入的元素在低的index中
  • PHP7中的Bucket現在直接保存一個zval, 取代了PHP5時代bucket中的pData和pDataPtr
  • PHP7中現在使用zend_string作為數組的字符串key,取代了PHP5時代bucket的*key, nKeylength

 

還要介紹一個東西:arData 通過數據的 index 保證了數據的有序性,數據 K-V 關系是使用了 arHash 來存儲,其中 arHash 可以通過 arData 取負的索引訪問到,介紹如下圖

 

PHP7 HashTable 的其他特點請閱讀 Laruence 深入理解PHP7內核之HashTable

PHP 7中新的Hashtable實現和性能改進

PHP internals Book

PHP 哈希表(數組)的內核實現

 

我的博客即將搬運同步至騰訊雲+社區,邀請大家一同入駐:https://cloud.tencent.com/developer/support-plan?invite_code=13ews1h77n5l4


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM