我们正在用C ++开发高性能的关键软件。在那里,我们需要一个并发的哈希映射并实现一个。因此,我们编写了一个基准来确定与并发哈希图相比要慢多少std::unordered_map
。
但是,这std::unordered_map
似乎太慢了……所以这是我们的微基准测试(对于并发映射,我们产生了一个新线程,以确保不会对锁定进行优化,并且请注意,我从不插入0,因为我也使用进行了基准测试google::dense_hash_map
,需要一个空值):
boost::random::mt19937 rng;
boost::random::uniform_int_distribution<> dist(std::numeric_limits<uint64_t>::min(), std::numeric_limits<uint64_t>::max());
std::vector<uint64_t> vec(SIZE);
for (int i = 0; i < SIZE; ++i) {
uint64_t val = 0;
while (val == 0) {
val = dist(rng);
}
vec[i] = val;
}
std::unordered_map<int, long double> map;
auto begin = std::chrono::high_resolution_clock::now();
for (int i = 0; i < SIZE; ++i) {
map[vec[i]] = 0.0;
}
auto end = std::chrono::high_resolution_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - begin);
std::cout << "inserts: " << elapsed.count() << std::endl;
std::random_shuffle(vec.begin(), vec.end());
begin = std::chrono::high_resolution_clock::now();
long double val;
for (int i = 0; i < SIZE; ++i) {
val = map[vec[i]];
}
end = std::chrono::high_resolution_clock::now();
elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - begin);
std::cout << "get: " << elapsed.count() << std::endl;
(编辑:整个源代码可以在这里找到:http : //pastebin.com/vPqf7eya)
结果为std::unordered_map
:
inserts: 35126
get : 2959
对于google::dense_map
:
inserts: 3653
get : 816
对于我们的手动并发映射(虽然基准是单线程的,但会锁定)-但在单独的生成线程中):
inserts: 5213
get : 2594
如果我在没有pthread支持的情况下编译了基准测试程序并在主线程中运行了所有程序,则对于我们的手动并发映射,将获得以下结果:
inserts: 4441
get : 1180
我用以下命令编译:
g++-4.7 -O3 -DNDEBUG -I/tmp/benchmap/sparsehash-2.0.2/src/ -std=c++11 -pthread main.cc
因此,特别是插入内容std::unordered_map
似乎非常昂贵-35秒,而其他地图则为3-5秒。而且查找时间似乎很长。
我的问题:这是为什么?我读了另一个关于stackoverflow的问题,有人问,为什么std::tr1::unordered_map
比自己的实现要慢。有最高评分的回答状态,即std::tr1::unordered_map
需要实现更复杂的接口。但是我看不到这种说法:我们在并发映射中std::unordered_map
使用了存储桶方法,也使用了存储桶方法(google::dense_hash_map
不是,但是std::unordered_map
应该至少比手工备份并发安全版本还快吗?)。除此之外,我在界面中看不到任何强制执行一项功能的功能,该功能使哈希映射的性能下降。
所以我的问题是:这真的std::unordered_map
很慢吗?如果不是:那是什么问题?如果是:原因是什么。
我的主要问题是:为什么要在std::unordered_map
如此昂贵的价格中插入一个值(即使我们一开始就保留了足够的空间,它的性能也不会好得多-因此重新哈希似乎不是问题)?
编辑:
首先:是的,提出的基准并不是完美无缺的-这是因为我们在基准上进行了很多uint64
尝试,并且仅仅是一个hack(例如,实际上,生成int 的分布不是一个好主意,在循环中排除0有点愚蠢等等...)。
目前大多数评论都说明,我可以通过为unordered_map预先分配足够的空间来使其更快。在我们的应用程序中这是不可能的:我们正在开发一个数据库管理系统,并且需要一个哈希映射来存储事务期间的一些数据(例如锁定信息)。因此,此映射可以是从1(用户仅执行一次插入并提交)到数十亿个条目(如果发生全表扫描)的所有内容。根本不可能在这里预分配足够的空间(而刚开始分配太多会消耗太多内存)。
此外,我很抱歉,我的问题还不够清楚:我对快速提高unordered_map(使用Google密集型哈希图对我们来说没什么用)并不感兴趣,我只是不太了解这种巨大的性能差异来自何处。它不能只是预分配(即使具有足够的预分配内存,密集映射也比unordered_map快一个数量级,我们的手动并发映射以大小为64的数组开始-因此比unordered_map小)。
那么,造成这种不良表现的原因是std::unordered_map
什么?或提出不同的要求:是否可以编写std::unordered_map
标准符合且(几乎)与Google密集哈希图一样快的接口的实现?还是在标准中有某种东西可以强迫实施者选择一种低效的实施方式?
编辑2:
通过分析,我发现很多时间用于整数除法。std::unordered_map
使用素数表示数组大小,而其他实现则使用2的幂。为什么要std::unordered_map
使用质数?如果哈希值不好,性能会更好吗?对于良好的哈希,它不会产生任何影响。
编辑3:
这些是数字std::map
:
inserts: 16462
get : 16978
Sooooooo:为什么插入的std::map
速度比插入的速度快std::unordered_map
?我的意思是WAT?std::map
具有较差的局部性(树与数组),需要进行更多的分配(每个插入vs每次哈希+每次碰撞加〜1),并且最重要的是:具有另一种算法复杂性(O(logn)vs O(1))!
SIZE
。