PHP7哈希表内部结构
PHP7 hashtable internal structure
有非常有效的关联。 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
这是为了让数字变成负数。 hash的布局table真是脑残(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 的补码是负数,目的是通过与此进行 ORring,并转换为 int32_t
得到 ht->arData
的负偏移量.然后将指向 Bucket 的指针类型 ht->arData
转换为指向 uint32_t
的指针,并使用负索引对该指针进行索引。 IE。所有这些可疑的诡计都不需要每个散列有 2 个指针 table,而是使用 1 个指向数据结构的中间。
使用 AND 并从 ht->arData
中减去的模数就足够了,并导致相同的操作 - 似乎这已经被手动优化以在一些糟糕的编译器上速度很快。
NikiC 写道:
这与 h & size -1
基本相同,但进入负数而不是正数
您没有将最高位设置为零,而是将它们设置为一
所以你得到一个介于 -1 和 -size
之间的负数
掩码为 -(大小 << 1)。与 ~(size << 1) + 1 或 ~((size << 1) - 1)
相同
所以基本上和你得到普通面具的方法一样,但是
a) inverted 因为你想设置高位而不是低位
b) 移动一位以便正确处理边界
有非常有效的关联。 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
这是为了让数字变成负数。 hash的布局table真是脑残(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 的补码是负数,目的是通过与此进行 ORring,并转换为 int32_t
得到 ht->arData
的负偏移量.然后将指向 Bucket 的指针类型 ht->arData
转换为指向 uint32_t
的指针,并使用负索引对该指针进行索引。 IE。所有这些可疑的诡计都不需要每个散列有 2 个指针 table,而是使用 1 个指向数据结构的中间。
使用 AND 并从 ht->arData
中减去的模数就足够了,并导致相同的操作 - 似乎这已经被手动优化以在一些糟糕的编译器上速度很快。
NikiC 写道:
这与 h & size -1
基本相同,但进入负数而不是正数
您没有将最高位设置为零,而是将它们设置为一 所以你得到一个介于 -1 和 -size
之间的负数掩码为 -(大小 << 1)。与 ~(size << 1) + 1 或 ~((size << 1) - 1)
相同所以基本上和你得到普通面具的方法一样,但是 a) inverted 因为你想设置高位而不是低位 b) 移动一位以便正确处理边界