【问题标题】:C++ - how to buffer calc results faster than using unordered_mapC++ - 如何比使用 unordered_map 更快地缓冲计算结果
【发布时间】:2021-05-30 17:31:01
【问题描述】:

我读了很多关于 unordered_map 速度不是很快的文章,但我想知道这样做的最佳选择是什么:

我需要为整数参数的函数缓冲计算结果。我不提前知道将要求什么范围或间隔。以最大分辨率存储在向量中会消耗太多内存。 所以我正在使用 unordered_map<unsigned long, pair<T, long>> 其中键是要计算的函数的参数,对中的第一个是类型 T 的计算结果,对中的第二个是该计算的版本信息。 只有当 unordered_map not 包含元素 它包含它但版本已过时,才会执行计算,然后将其添加到 unordered_map。查找函数如下所示:

template<typename T> class BufferClass{
  long MyVersion;
  unordered_map<unsigned long, pair<T,long>> Buffer;
public:
  BufferClass(): MyVersion{1} {};

  T* GetIfValid(unsigned long index)
  {
    if (!Buffer.count(index)) return nullptr;
    pair <T,long> &x{Buffer.at(index)};
    if (x.second!=MyVersion) return nullptr;
    return &x.first;
  }
/* ...Functions to set elements...*/
}

如您所见,我将元素有效性检查和检索结合在一个函数中,因此我只需要对两者进行一次查找。

分析器显示大部分计算时间都用在了与 unordered_map 相关的哈希函数 __constrain_hash 中。

这样存储和检索值的最快方法是什么?存储的索引列表预计是不连续的(会有很多“漏洞”),第一个和最后一个索引也大多是未知的。

T 通常是“小”数据类型(如双精度或复数)。

谢谢!

马丁

【问题讨论】:

  • "分析器显示大部分计算时间都用在了与 unordered_map 相关的哈希函数 __constrain_hash 中。" ... 和?分析器将始终显示大部分计算时间在某处用完。问题是是否花费了太多时间。所以……是吗?您多久调用一次?您希望在一秒钟内进行多少次此类调用?
  • 存在平面地图实现,它们在非常特定的使用模式下可以比 unordered_map 执行得更好,但它们都有权衡和限制。一般来说,坚持使用通用哈希映射,例如std::unordered_map (并修复您对它的使用以更优化,如答案中所述)

标签: c++ c++17 unordered-map


【解决方案1】:

在您的代码中,一个查询中可能有两个哈希查找,一个在count() 中调用,另一个在at() 中调用。这是多余的,请改用unordered_map::find,请参阅here

示例代码:

const auto iter = Buffer.find(index);
if(iter != Buffer.end()) //Found something, so the return value is not end()
{
    return &(iter->first);
}
else return nullptr;

在我看来,unordered_map 很慢,但没有那么慢,因为 99.9% 的使用率已经足够快了。您可能需要检查您是否(不必要地)调用此函数太多次。使用其他快速实现不是免费的,它可能会使您的代码库膨胀,损害您的应用程序与不同主机系统的兼容性等等。如果您认为std::unordered_map 速度慢得不合理,那几乎总是因为您在工作中出现了错误。 (您的估计或您的代码实现)

顺便说一句,还有一点要提一下:你说 T 是一种小数据类型,对吧?然后返回它的值而不是指向它的指针,这样更快更安全。

【讨论】:

    【解决方案2】:

    你的实现让我觉得奇怪的是以下两行:

        if (!Buffer.count(index)) return nullptr;
        pair <T,long> &x{Buffer.at(index)};
    

    此代码正在检查密钥是否存在,然后丢弃结果并再次搜索相同的密钥边界检查以启动。我想你会发现用std::unordered_map&lt;unsigned long, std::pair&lt;T, long&gt;&gt;::find 搜索一次并重用结果会更好:

        auto it = Buffer.find(index);
        if (it == Buffer.end()) return nullptr;
        auto& x = *it;
    

    【讨论】:

    • "启动边界检查" 边界检查无序地图不需要任何费用;它要么找到该项目,要么没有。所以使用at 只会添加条件throw
    • @NicolBolas 充其量只是一个额外的分支。他们声称std::unordered_map 太慢了。如果他们那么关心性能,他们也应该删除不必要的分支逻辑。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-01
    • 1970-01-01
    • 2019-05-25
    • 2018-06-06
    • 2018-09-13
    • 1970-01-01
    相关资源
    最近更新 更多