【问题标题】:Avoiding multiple lookups in map/unordered_map避免在 map/unordered_map 中进行多次查找
【发布时间】:2018-02-06 20:28:30
【问题描述】:

假设我们有一个昂贵的函数映射 stringint 并且想要将结果缓存在一个映射中。

最简单的代码是

int mapStringToIntWithCache(std::string const& s) {
    static std::unordered_map<std::string, int> cache;
    if (cache.count(s) > 0) return cache[s];
    else return cache[s] = myExpensiveFunction(s);
}

但这有 2 次查找。

因此我倾向于写这个

int mapStringToIntWithCache(std::string const& s) {
    static std::unordered_map<std::string, int> cache;
    size_t sizeBefore = cache.size();
    int& val = cache[s];
    if (cache.size() > sizeBefore) val = myExpensiveFunction(s);
    return val;
}

这只有一个查找,但看起来有点笨拙。有没有更好的办法?

【问题讨论】:

  • 第一个 sn-p 不需要两个以上的查找吗?它有两次countcache[s]
  • @user463035818 count 是第一个查找,然后是两个 [s] 查找之一,但只有一个。所以每次运行两次查找。
  • 是的,我没有仔细阅读。我不得不承认我的第一反应是:过早优化。然而,一旦你决定使用缓存,它就不再为时过早......
  • 如果您关心性能,请不要通过 const 左值引用传递参数。如果插入发生,这将阻碍从右值 string 参数移动。使用,例如,完美转发。

标签: c++ performance dictionary stdmap


【解决方案1】:

只需使用std::map::emplace() 方法:

int mapStringToIntWithCache(std::string const& s) {
    static std::unordered_map<std::string, int> cache;
    auto pair = cache.emplace( s, 0 );
    if( pair.second )
         pair.first->second = myExpensiveFunction(s);
    return pair.first->second;
}

【讨论】:

  • 请注意,如果 C++17 可用,您也可以使用 try_emplace。哪个更好,因为emplace 可能会复制s 字符串参数。
  • 假设它实际上是存储的int,为什么不insert
  • @TheVee insert 需要编写更长的代码。有什么原因吗?
  • @DanielLangr 在这种情况下看不到任何区别,如果发生插入,任何一方都必须复制密钥,否则双方都不会复制它。
  • @Slava 我对此不确定,但认为emplace 可能会复制密钥,即使没有发生插入。稍后将与 Standard 核对,现在我正在观看 Falcon Heavy 的发射 :)。引用 cpprefernece.com 的形式:“即使容器中已经有一个带有键的元素,也可以构造该元素,在这种情况下,新构造的元素将立即销毁。”
【解决方案2】:

请注意@Slava 的回答:如果您通过 const 左值引用传递参数,那么如果它是右值,则不能从该参数中移动:

int i = mapStringToIntWithCache("rvalue argument here");

如果插入到cache,临时的std::string 参数将在此处复制

您可以使用 完美转发,但是,如果您希望仅将参数保持为 std::string 类型(例如,对于字符串文字的隐式转换),那么您需要一些 wrapper-helper 函数 解决方案:

template <typename T>
int mapStringToIntWithCacheHelper(T&& s) {
  static std::unordered_map<std::string, int> cache;
  auto pair = cache.emplace( std::forward<T>(s), 0 );
  if( pair.second )
    pair.first->second = myExpensiveFunction(pair.first->first); // can't use s here !!!
  return pair.first->second;
}

int mapStringToIntWithCache(const std::string & s) {
  mapStringToIntWithCacheHelper(s);
}

int mapStringToIntWithCache(std::string && s) {
  mapStringToIntWithCacheHelper(std::move(s));
}

【讨论】:

    猜你喜欢
    • 2019-05-24
    • 2015-06-12
    • 1970-01-01
    • 2022-07-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-23
    • 1970-01-01
    相关资源
    最近更新 更多