【问题标题】:PHP7 hashtable internal structurePHP7 hashtable内部结构
【发布时间】:2019-05-27 08:37:21
【问题描述】:

有非常高效的关联。 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

【问题讨论】:

  • 除法总是比按位或运算符更昂贵。你发的两个操作是不一样的……散列函数只有一个要求:在索引键的空间中尽可能均匀地散布散列值。

标签: php c php-internals


【解决方案1】:

这是使数字为负数。哈希表的布局真是脑残(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 个指针,而是使用指向数据结构中间的 1 个指针。

使用 AND 并从 ht->arData 中减去的模就足够了,并且会产生相同的操作 - 似乎这已经过手动优化,以便在某些糟糕的编译器上运行得很快。

【讨论】:

    【解决方案2】:

    NikiC 写道: 这与h & size -1 基本相同,但使用负数而不是正数

    不是将最高位设置为零,而是将它们设置为一 所以你得到一个介于 -1 和 -size 之间的负数

    掩码为 -(size 相同

    所以基本上与您获得通常面具的方式相同,但是 a) 倒置,因为您希望设置顶部位而不是底部位,并且 b) 移动一,以便正确处理边界

    【讨论】:

      猜你喜欢
      • 2012-12-12
      • 2015-11-01
      • 1970-01-01
      • 2019-01-08
      • 2021-07-12
      • 1970-01-01
      • 1970-01-01
      • 2020-07-02
      • 2013-03-19
      相关资源
      最近更新 更多