【问题标题】:more efficient structure as unordered_map<pair<int, int>, int>更有效的结构为 unordered_map<pair<int, int>, int>
【发布时间】:2014-09-01 18:37:53
【问题描述】:

我有大约 20,000,000 个 pair&lt;int, int&gt; 需要关联到 ints。我用unordered_map&lt;pair&lt;int, int&gt;, int&gt; 这样做了。分析我的算法表明检查条目是否存在

bool exists = myMap[make_pair(a, b)] != NULL

是性能瓶颈。我认为从unordered_map 检索这些信息会非常快,因为它是 O(1)。但是如果常数很大,常数时间可能会很慢......

我的哈希函数是

template <>
struct tr1::hash<pair<int, int> > {
public:
        size_t operator()(pair<int, int> x) const throw() {
             size_t h = x.first * 1 + x.second * 100000;
             return h;
        }
};

你知道我的问题有什么更好的数据结构吗?

显然我不能只将信息存储在矩阵中,因此内存量不适合现有的任何计算机。我所知道的分布是myMap[make_pair(a, a)] 不存在任何a。并且所有ints 都在从 0 到大约 20,000,000 的连续范围内。

将其视为一个稀疏的 20,000,000x20,000,000 矩阵,其中包含大约 20,000,000 个条目,但从不在主对角线上。

建议

vector&lt;pair&lt;int, int&gt;&gt;*(具有 N 个条目的数组)是否会更快? a 的查找将是微不足道的(只是数组的索引),然后我将遍历该向量,将该对的 first 值与 b 进行比较。

大更新

我上传了raw data,这样你就可以看到结构了。

【问题讨论】:

  • 对中的整数必须是可检索的吗?还是可以创建两个地图? umap&lt;some_key, int&gt;umap&lt;some_key, pair&lt;&gt;&gt;?
  • 请注意,myMap[make_pair(a, b)] != NULL 不会像您认为的那样做。如果它不存在,它会插入该对,并将映射的值与0 (这是NULL 扩展的内容)进行比较。它根本不检查是否存在。在现代 C++ 中,你永远不应该使用 NULL
  • 我们需要了解您的算法才能找到更好的数据结构。也许你只需要一个二维数组,但也许你需要别的东西。
  • 如果您对数据分布有所了解,您可以提供自己的哈希函数,以尽可能减少冲突。
  • @a_guest std::unordered_map 是哈希图,而不是树。那是std::map

标签: c++ performance algorithm map unordered-map


【解决方案1】:

作为建议,我选择了带有 N 个条目的 vector&lt;pair&lt;int, int&gt;&gt;*。它比unordered_map 快大约 40%。

【讨论】:

    【解决方案2】:

    主要的事情绝对是避免在每次搜索时添加默认构造的元素:

    bool exists = myMap[make_pair(a, b)] != NULL; // OUCH
    
    bool exists = myMap.find(make_pair(a, b)) != myMap.end();  // BETTER
    
    iterator i = myMap.find(make_pair(a, b);
    if (i != myMap.end()) ... else ...;      // MAY BE BEST - SEE BELOW
    

    还有伟大的哈希挑战......哇哦!这可能值得一试,但很大程度上取决于对中的数字是如何分布的以及您的实现的std::hash(通常是传递!):

        size_t operator()(pair<int, int> x) const throw() {
             size_t hf = std::hash(x.first);
             return (hf << 2) ^ (hf >> 2) ^ std::hash(x.second);
        }
    

    如果您将这对替换为int64_ts,您可能也会发现它更快,因此关键比较绝对是简单的整数比较,而不是级联。

    还有,你在生存测试之后在做什么?如果您需要访问/更改与同一键关联的值,则应保存迭代器 find 返回并避免再次搜索。

    【讨论】:

    • 我现在正在使用 Angew 的哈希函数,它基本上是在比较 int64_t(见下面他的回答)。我需要访问该值,但由于大多数查找的对不存在,这并不重要。
    • @user2033412:我有理由认为我的哈希函数可能比 Angew 的更好(或者我看到他的之后就不会费心发布它)——它是技术性的,但最终如果你在乎的话关于速度,您应该测量两者然后选择。上面说64位unordered_map键的时候,我说的不是hash的宽度,而是把键中的两个int组合成一个int64_t,然后存到表里,然后提取之后再一次,或者存储一个类型为 unionint64_tint32_t
    • 另外,您可以使用某种布隆过滤器来消除许多失败的查找,而无需搜索哈希表。关于数据,你还有什么要告诉我的吗?对于任何给定的.first 值的重复次数,你会得到什么样的直方图?显然平均值是一个重复,但曲线的形状是什么? .second 的曲线是否相似? .first.second 完全不相关吗?
    • 我目前正在创建一个包含完整原始数据集的文本文件,格式如下(每行一个条目):[a,b] -> c。我会尽快上传并发布链接。
    • 你能看一下吗,@tony-d?
    【解决方案3】:

    首先,myMap[make_pair(a, b)] != NULL 并没有按照你的想法去做。如果它不存在,它将插入该对,并将映射的值与 0 进行比较(这是 NULL 扩展的内容)。它根本不检查是否存在。 (请注意,在现代 C++ 中,您永远不应该使用 NULL。使用 0 表示数字,使用 nullptr 表示指针)。

    至于主题,您的哈希函数似乎不太好。不要忘记ints 上的算术运算是在ints 中完成的。由于在大多数编译器上int 是 32 位的,它的最大值略高于 2,000,000,000。所以 20,000,000 * 10,000 比这大得多,导致溢出(和未定义的行为)。

    鉴于您的数据数量,我假设您使用的是 64 位平台,这意味着 size_t 的长度为 64 位。因此,使用这样的哈希函数可能会得到更好的结果:

    size_t operator()(pair<int, int> x) const throw() {
         size_t f = x.first, s = x.second;
         return f << (CHAR_BIT * sizeof(size_t) / 2) | s;
    }
    

    与您现在所拥有的相比,这应该会产生明显更少的碰撞(并且已经定义了行为)。

    如果这没有帮助,您也可以尝试两步法:

    std::unordered_map<int, std::unordered_map<int, int>>
    

    先查找x.first,然后查找x.second。我不知道这是否有帮助;测量和观察。

    【讨论】:

    • 通过使用你的散列函数,算法变得非常慢......就像......慢了 100 多倍!我真的很惊讶!
    • @user2033412 很有趣。您是否尝试过在地图的find() 中进行分析以找出为什么需要这么长时间(即使在您的原始版本中)?作为健全性检查,您正在运行优化的代码,对吧?
    • 正在运行优化代码。分析在插入过程中显示以下内容(创建 umap 也非常慢...):profiler
    • sizeof(size_t) / 2 将类似于 4。您可能想要 CHAR_BIT * sizeof(size_t) / 2。
    • 我刚刚尝试了 Falk Huffner 的更正版本。现在它的运行速度与原始版本一样快。这个版本根本不应该产生任何冲突,因此应该尽可能地提高性能,对吧?
    【解决方案4】:

    我建议您使用更好的哈希函数进行测试。如果您在 SO 上搜索此处可以找到示例,但这是一种可能的实现方式。

    struct pair_hash {
        template <typename T1, typename T2>
        size_t operator()(const std::pair<T1, T2> &pr) const {
            using std::hash;
            return hash<T1>()(pr.first) ^ hash<T2>()(pr.second);
        }
    };
    

    【讨论】:

    • 这将在矩阵的主对角线(即(0,0),(1,1),...)上产生碰撞,其中通常元素被认为是非零的。
    • @a_guest 来自 OP:我所知道的关于分发的所有信息都是 myMap[make_pair(a, a)] 不存在任何 a
    • @a_guest:没错,您总是可以移动或补充其中一个哈希值以减少该问题。
    • 我刚试了一下,比原来的hash-function慢了一点。
    【解决方案5】:

    您是否尝试过使用 myMap.find(make_pair(a,b)) != myMap.end()operator[] 如果元素不存在则创建它。我希望find 更快。

    【讨论】:

    • 我只是尝试了find[],但它没有任何改变。 :-(
    • 你让我开心!谢谢你!我已经在 codechef 上尝试了 2 天这个问题,这是我需要的优化!非常感谢!!
    猜你喜欢
    • 2021-11-18
    • 2020-10-22
    • 2010-12-09
    • 2011-06-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多