gpt4 book ai didi

c++ - 为什么LLVM选择开放寻址哈希表来实现llvm::StringMap?

转载 作者:塔克拉玛干 更新时间:2023-11-03 01:50:44 26 4
gpt4 key购买 nike

许多消息来源说 open-addressingllvm::StringMap 中使用的散列冲突处理方法不稳定。据说当负载系数很高(这是可以想象的)时,开放寻址不如链接

但是如果负载因子低,开放寻址会造成巨大的内存浪费,因为我必须在内存中分配 Bucket_number * sizeof(Record) 字节,即使大多数桶都没有记录。

所以我的问题是,LLVM 选择开放寻址而不是分离链的原因是什么?仅仅是因为缓存局部性带来的速度优势(记录本身存储在桶中)吗?

谢谢:)

编辑:C++11 标准对 std::unordered_setstd::unordered_map 的要求暗示了链接方法,而不是开放寻址。为什么LLVM会选择一种连C++标准都达不到的hash冲突处理方式呢? llvm::StringMap 是否有任何特殊用例可以保证这种偏差? (编辑:这个 slide deck 将几种 LLVM 数据结构的性能与 STL 对应物的性能进行了比较)

另一个问题,顺便说一句:

llvm::StringMap 如何保证在增长时不重新计算键的哈希值?manual说:

hash table growth does not recompute the hash values for strings already in the table.

最佳答案

让我们看看the implementation .在这里我们看到该表存储为间接指针记录的并行数组以及任何缓存的 32 位哈希码数组,即单独的结构数组。

有效:

struct StringMap {
uint32_t hashcode[CAPACITY];
StringMapEntry *hashptr[CAPACITY];
};

除了容量是动态的并且负载系数似乎保持在容量的 37.5% 到 75% 之间。

对于 N 记录一个负载因子 F 这会产生 N/F 指针加上 N/F 整数与 N*(1+1/F) 指针和等效链式实现的 N 整数相比,开放寻址实现。在典型的 64 位系统上,开放地址版本的大小在 ~4% 到 ~30% 之间较小

然而,正如您所怀疑的那样,这里的主要优势在于缓存效果。除了平均缓存通过缩小数据来减少争用之外,冲突过滤归结为对连续 32 位哈希键的线性重新探测,而不检查任何进一步的信息。因此,在链接必须跟随到可能未缓存的存储的链式情况下,拒绝冲突要快得多,因此可以使用显着更高的加载因子。另一方面,必须在指针查找表上进行一次额外的可能缓存未命中,但这是一个常数,不会随着相当于一次链式冲突的负载而降低。

有效:

StringMapEntry *StringMap::lookup(const char *text) {
for(uint32_t *scan = &hashcode[hashvalue % CAPACITY]; *scan != SENTINEL; ++scan) {
uint32_t hash_value = hash_function(text);
if(hash_value == *scan) {
StringMapEntry *entry = p->hashptr[scan - hashcode];
if(!std::strcmp(entry->text, text))
return entry;
}
}
}
}

加上包装等细微之处。

关于你的第二个问题,优化是预先计算和存储哈希键。这会浪费一些存储空间,但可以防止检查可能很长的可变长度字符串的昂贵操作,除非几乎可以确定匹配。在退化的情况下,复杂的模板名称修改可能有数百个字符。

进一步优化 RehashTable是使用二次幂而不是素数表大小。这确保了有效地增长表会带来一个额外的哈希码位发挥作用,并将加倍的表去交织成两个连续的目标数组,有效地使操作成为缓存友好的线性扫描。

关于c++ - 为什么LLVM选择开放寻址哈希表来实现llvm::StringMap?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45387613/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com