PHP7哈希表内部结构

时间:2018-12-30 10:23:25

标签: php c php-internals

有一个非常有效的关联。 php源代码中使用的数组C语言实现。

/*
 * HashTable Data Layout
 * =====================
 *
 *                 +=============================+
 *                 | HT_HASH(ht, ht->nTableMask) |
 *                 | ...                         |
 *                 | HT_HASH(ht, -1)             |
 *                 +-----------------------------+
 * ht->arData ---> | Bucket[0]                   |
 *                 | ...                         |
 *                 | Bucket[ht->nTableSize-1]    |
 *                 +=============================+
 */

结构:

typedef struct _Bucket {
    zval              val;
    zend_ulong        h;                /* hash value (or numeric index)   */
    zend_string      *key;              /* string key or NULL for numerics */
} Bucket;

typedef struct _zend_array 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;
};

示例功能:

static zend_always_inline Bucket *zend_hash_find_bucket(const HashTable *ht, zend_string *key)
{
    zend_ulong h;
    uint32_t nIndex;
    uint32_t idx;
    Bucket *p, *arData;

    h = zend_string_hash_val(key);
    arData = ht->arData;
    nIndex = h | ht->nTableMask; //index calculation
    idx = HT_HASH_EX(arData, nIndex);
    while (EXPECTED(idx != HT_INVALID_IDX)) {
        p = HT_HASH_TO_BUCKET_EX(arData, idx);
        if (EXPECTED(p->key == key)) { /* check for the same interned string */
            return p;
        } else if (EXPECTED(p->h == h) &&
             EXPECTED(p->key) &&
             EXPECTED(ZSTR_LEN(p->key) == ZSTR_LEN(key)) &&
             EXPECTED(memcmp(ZSTR_VAL(p->key), ZSTR_VAL(key), ZSTR_LEN(key)) == 0)) {
            return p;
        }
        idx = Z_NEXT(p->val);
    }
    return NULL;
}

h是由哈希函数返回的大整数。

问题是: 为什么用这种方式进行索引计算?

nIndex = h | ht->nTableMask; //index calculation

为什么哈希表大小上除以h整数的余数不是简单的?

nIndex = h & (ht->nTableSize - 1); //analog: nIndex = h % ht->nTableSize

2 个答案:

答案 0 :(得分:3)

这是为了使数字为负。哈希表的布局确实让人脑筋急转(Zend/zend_types.h):

/*
 * HashTable Data Layout
 * =====================
 *
 *                 +=============================+
 *                 | HT_HASH(ht, ht->nTableMask) |
 *                 | ...                         |
 *                 | HT_HASH(ht, -1)             |
 *                 +-----------------------------+
 * ht->arData ---> | Bucket[0]                   |
 *                 | ...                         |
 *                 | Bucket[ht->nTableSize-1]    |
 *                 +=============================+
 */

ht->nTableMask是一个整数,解释为2的补码为负数,其目的是通过与之进行或运算并转换为int32_t,您将从ht->arData获得负偏移。然后将类型为Bucket的ht->arData强制转换为指向uint32_t的指针,并使用负索引对该指针进行索引。即所有这些可疑的技巧都是存在的,每个哈希表不需要有2个指针,而是使用1个指向数据结构中间的指针。

使用AND并从ht->arData中减去的模数就足够了,并导致相同的操作-似乎已经手动优化了它,可以在某些错误的编译器上快速实现。

答案 1 :(得分:1)

NikiC写道: 这基本上与display:flex相同,但是要用负数代替正数

不是将高位设置为零,而是将它们设置为一 这样您得到的负数介于-1和-size之间

掩码为-(大小<< 1)。与〜(size << 1)+ 1或〜((size << 1)-1)

相同

因此,您获得通常面膜的方式基本上相同,但是 a)倒置,因为您要设置高位而不是底部和 b)偏移一,以便正确处理边界