问题描述
考虑这个代码。我为 unordered_map 保留 6 个点并插入 6 个元素。之后,有7个桶。为什么是这样? max_load_factor 为 1,并且有足够的桶用于我插入的元素数量。
#include <iostream>
#include <unordered_map>
using namespace std;
int main () {
unordered_map<std::string,std::string> mymap = {
{"house","maison"},{"apple","pomme"},{"tree","arbre"},{"book","livre"},{"door","porte"},{"grapefruit","pamplemousse"}
};
unordered_map<std::string,std::string> mymap2; // THIS ONE!!!
mymap2.reserve(6);
for (auto i:mymap) {
mymap2[i.first] = i.second;
}
std::cout << "max_load factor " << mymap2.max_load_factor() << " mymap has " << mymap2.bucket_count() << " buckets.\n";
for (unsigned i=0; i<mymap2.bucket_count(); ++i) {
cout << "bucket #" << i << " contains: ";
for (auto it = mymap2.begin(i); it!=mymap2.end(i); ++it)
cout << "[" << it->first << ":" << it->second << "] ";
cout << endl;
}
return 0;
}
输出:
max_load factor 1 mymap has 7 buckets.
bucket #0 contains:
bucket #1 contains: [book:livre]
bucket #2 contains: [tree:arbre]
bucket #3 contains: [house:maison] [grapefruit:pamplemousse]
bucket #4 contains:
bucket #5 contains: [door:porte]
bucket #6 contains: [apple:pomme]
解决方法
cplusplus.com website 给出了这样的解释:
void reserve (size_type n);
请求容量更改
将容器 (bucket_count
) 中的桶数设置为最适合包含至少 n 个元素。
如果 n 大于当前 bucket_count
乘以 max_load_factor
,则容器的 bucket_count
会增加并强制重新散列。
如果 n 小于该值,则该函数可能无效。
在您声明 unordered_map
变量时,它的 bucket_count
为 1
,max_load_factor
为 1
。
然后你 reserve
6
个桶大于 max_load_factor
乘以 bucket_count
根据这个定义,在我看来,这种行为是正确的。
我在您的代码的第 17
行添加了以下行以在 bucket_count
之前显示 reserve
,实际上,它是 1
std::cout << "BEFORE RESERVE max_load factor " << mymap2.max_load_factor() << " mymap has " << mymap2.bucket_count() << " buckets.\n";
显示如下:
BEFORE RESERVE max_load factor 1 mymap has 1 buckets.
预留后:
AFTER RESERVE max_load factor 1 mymap has 7 buckets.
因此,在我看来,这种行为是正常的。
,哈希表实现倾向于在两个理想的品质之间进行选择:
-
保持 2 的幂
bucket_count()
(即,如有必要,将传递给 Reserve 的任何值四舍五入为下一个 2 的幂),所以-
可以映射哈希函数返回的
size_t
值 使用 1-CPU-cycle 按位 AND 运算到桶的范围 (例如 8 个桶 -> 哈希值与 7 相加); -
这具有从哈希值中切掉高位位的不良影响,因此它们无助于随机化桶的放置
-
Visual C++ 做到这一点
-
-
保持素数
bucket_count()
-
这具有非常理想的副作用,即散列值中的高阶位会影响桶的选择,因此较低质量(即更快)的散列函数仍然经常管理更平等、更少冲突/容易聚集,桶放置
-
天真地实现,这会强制编译器通过运行时变量
bucket_count()
执行 mod ("%") 操作,这可能需要例如40-90 个 CPU 周期,取决于 CPU。一种更快的替代方法是使用在调整哈希表大小时使用的质数表中的索引,通过该硬编码的常量质数值切换到模运算,因此编译器可以尝试使用位移、减法或加法来优化模,或者必要时乘法(您可以在 this snippet on godbolt 中看到可能的优化类型) -
GCC 这样做;我认为 clang 也是。
-
所以,总而言之 - 当您要求 6 个存储桶时,GCC 或 clang 会将其增加到某个质数 - 不一定是下一个,但在这种情况下似乎发生了 - 以减少稍后插入元素时的冲突倾向.