【问题标题】:Why is std::unordered_map slow, and can I use it more effectively to alleviate that?为什么 std::unordered_map 很慢,我可以更有效地使用它来缓解这种情况吗?
【发布时间】:2017-07-24 02:49:14
【问题描述】:

我最近发现了一件奇怪的事情。似乎用no caching at all 计算 Collat​​z 序列长度比using std::unordered_map to cache all elements 快 2 倍以上。

请注意,我确实从问题 Is gcc std::unordered_map implementation slow? If so - why? 中得到了提示,并且我尝试利用这些知识使 std::unordered_map 表现得尽可能好(我使用了 g++ 4.6,它的性能确实比最新版本的 g++ 更好,我尝试了为了指定一个合理的初始桶数,我将其设置为完全等于地图必须包含的最大元素数)。

相比之下,using std::vector to cache a few elements 几乎比不使用缓存快 17 倍,比使用 std::unordered_map 快近 40 倍。

是我做错了什么还是这个容器太慢了,为什么?可以让它执行得更快吗?或者,hashmap 本质上是无效的,应该在高性能代码中尽可能避免使用?

有问题的基准是:

#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

【问题讨论】:

  • @WhiZTiM 我做错了什么还是这个容器很慢,为什么?
  • unordered_map 只是对于很多用途来说相当慢。大多数时候std::vector 是最快的容器。数据位置 > 其他一切。
  • @WhiZTiM 编辑了问题,现在更好了吗?
  • 如果您遇到复杂性问题,我为您感到难过,std::unordered_map 在 Big-O(N) 中返回。 (worst case)
  • 你检查过实际值吗?鉴于 999999 的一半大于 65536,因此使用 uint_fast16_t 缓存值 IMO 是可疑的。

标签: c++ performance c++11 caching unordered-map


【解决方案1】:

标准库的映射确实天生就很慢(std::map 尤其如此,但std::unoredered_map 也是如此)。 Google 的 Chandler Carruth 在他的 CppCon 2014 talk 中解释了这一点;简而言之:std::unordered_map 对缓存不友好,因为它使用链表作为存储桶。

This SO question 提到了一些有效的哈希映射实现 - 请改用其中之一。

【讨论】:

  • 链接的谈话确实是一个非常值得观看的视频。这家伙对性能问题的理解比绝大多数认为自己了解性能问题的人要深入得多。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-03-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-28
  • 2016-03-04
  • 1970-01-01
相关资源
最近更新 更多