unordered_map:为什么范围操作效率低下,这是事实?

时间:2016-04-13 11:59:46

标签: c++

我得到this example关于在C ++中实现泛型memoization。但是,正如有人在此评论中发出通知,原始代码进行了2次查找,而下面的代码只有一次。

template <typename ReturnType, typename... Args>
std::function<ReturnType (Args...)> memoize(std::function<ReturnType (Args...)> func)
{
    std::map<std::tuple<Args...>, ReturnType> cache;
    return ([=](Args... args) mutable  {
            std::tuple<Args...> t(args...);
auto range = cache.equal_range(t);
if (range.first != range.second) return (*range.first).second;
return (*cache.insert(range.first, func(args...))).second;

    });
}

有人注意到使用unordered_map可能会有更好的表现。但是我read

  对于通过子集的范围迭代,它们通常效率较低   他们的元素。我不明白为什么范围操作会更少   有效的,如果上述情况是这些情况之一(因为我们使用   范围)?

1 个答案:

答案 0 :(得分:1)

  

当有人在此评论中发出通知时,原始代码会进行2次查找,而下面的代码只会进行一次

好的,所以你试图使用提示的insert来避免插入点的冗余对数复杂性搜索。您仍然遇到与其他问题相同的错误,您的代码可能应该如下所示:

cache.insert(range.first, std::make_pair(t, func(args...)));
//           ^hint        ^value_type

(这是链接文档中的重载#4)。

你可以完全没有第一次查找:如果密钥存在,insert只返回现有元素的迭代器,所以你可以用乐观插入来写它 - 这是否更好取决于成本default-construct然后分配你的ReturnType:

auto result = cache.insert(make_pair(t, ReturnType{}));
if (result.second) {
    // insertion succeeded so the value wasn't cached already
    result.first->second = func(args...);
}
return result.first->second;
  

...使用unordered_map可能会有更好的表现......

std::map查找/插入具有对数复杂度的比例,std::unordered_map以恒定复杂度进行比例缩放。实际上哪个更好取决于你有多少条目,以及其他因素,你需要进行分析以确定。

  

... [unordered_map]对范围迭代的效率较低......

好吧,如果你避免equal_range,你根本不需要范围迭代。

查看您实际使用的操作,并了解哪种数据结构非常适合。对于简单的查找和插入,这些都是你真正需要的,unordered_map可能很好。如果您关心密钥的相对排序,那么std::map会更好。

两种结构对密钥的要求也不同:map需要排序(operator<或自定义比较器),而unordered_map需要定义良好的散列函数{{1或者自定义谓词。

首先,我不确定operator==是否支持std::hash,因此您可能需要提供自定义哈希来使用std::tuple