为什么 std::unordered_map 很慢,我可以更有效地使用它来缓解这种情况吗?

Why is std::unordered_map slow, and can I use it more effectively to alleviate that?

本文关键字:情况 有效地 map unordered std 很慢 为什么 我可以      更新时间:2023-10-16

我最近发现了一件奇怪的事情。似乎在完全没有缓存的情况下计算 Collatz 序列长度比使用std::unordered_map缓存所有元素快 2 倍以上

注意我确实从问题中得到了提示 gcc std::unordered_map 实施速度慢吗?如果是这样 - 为什么?我试图利用这些知识使std::unordered_map表现得尽可能好(我使用了 g++ 4.6,它确实比最新版本的 g++ 表现得更好,我试图指定一个合理的初始桶计数,我让它完全等于地图必须容纳的最大元素数)。

相比之下,使用std::vector缓存一些元素几乎比完全不缓存快 17 倍,比使用std::unordered_map快近 40 倍。

我做错了什么还是这个容器那么慢,为什么?它可以更快地执行吗?或者,也许哈希图本质上是无效的,应该在高性能代码中尽可能避免?

有问题的基准是:

#include <iostream>
#include <unordered_map>
#include <cstdint>
#include <ctime>
std::uint_fast16_t getCollatzLength(std::uint_fast64_t val) {
static std::unordered_map <std::uint_fast64_t, std::uint_fast16_t> cache ({{1,1}}, 2168611);
if(cache.count(val) == 0) {
if(val%2 == 0)
cache[val] = getCollatzLength(val/2) + 1;
else
cache[val] = getCollatzLength(3*val+1) + 1;
}
return cache[val];
}
int main()
{
std::clock_t tStart = std::clock();
std::uint_fast16_t largest = 0;
for(int i = 1; i <= 999999; ++i) {
auto cmax = getCollatzLength(i);
if(cmax > largest)
largest = cmax;
}
std::cout << largest << 'n';
std::cout << "Time taken: " << (double)(std::clock() - tStart)/CLOCKS_PER_SEC << 'n';
}

它输出:Time taken: 0.761717

而完全没有缓存的基准测试:

#include <iostream>
#include <unordered_map>
#include <cstdint>
#include <ctime>
std::uint_fast16_t getCollatzLength(std::uint_fast64_t val) {
std::uint_fast16_t length = 1;
while(val != 1) {
if(val%2 == 0)
val /= 2;
else
val = 3*val + 1;
++length;
}
return length;
}
int main()
{
std::clock_t tStart = std::clock();
std::uint_fast16_t largest = 0;
for(int i = 1; i <= 999999; ++i) {
auto cmax = getCollatzLength(i);
if(cmax > largest)
largest = cmax;
}
std::cout << largest << 'n';
std::cout << "Time taken: " << (double)(std::clock() - tStart)/CLOCKS_PER_SEC << 'n';
}

输出Time taken: 0.324586

标准图书馆的地图确实天生很慢(std::map特别是,但也std::unoredered_map)。谷歌的钱德勒·卡鲁斯(Chandler Carruth)在他的CppCon 2014演讲中解释了这一点;简而言之:std::unordered_map缓存不友好,因为它使用链表作为存储桶。

这个SO问题提到了一些有效的哈希映射实现 - 改用其中一个。