这是因为两次查找而不是一次而变慢了吗?

时间:2014-09-20 15:16:27

标签: c++ performance lookup unordered-map

当我想确保我想要使用的条目存在时,我通常会这样做。

#include <unordered_map>

struct type { int member; };
std::unordered_map<type> map;

if (map.find(key) != map.end())
    map[key].member = 42;

但是,我认为它在哈希映射中对key执行两次查找。这会缓存查找。

#include <unordered_map>

struct type { int member; };
std::unordered_map<type> map;

auto find = map.find(key);
if (find != map.end())
    find->second.member = 42;

第一种选择感觉更具表现力。真的慢了吗?

5 个答案:

答案 0 :(得分:22)

它可能会变慢,但可能不会(你现在正在进行额外的写入&#34;加速&#34;)但是当编写代码。写清楚的表达代码。然后,如果您的程序确实太慢,请在其上运行分析工具并找到您的瓶颈。如果此代码 实际上是真正的问题,那么只有尝试你的&#34;加速&#34;看看是否重要。

答案 1 :(得分:15)

是的,因为您搜索了两次密钥:map[key]搜索与map.find完全相同的密钥,并将结果丢弃。

就像打开一个抽屉看看是否有一个给定的物体,说“啊是的!”并关闭抽屉,然后再打开它并研究对象以改变它。

第二个代码打开抽屉,搜索一个对象并进行更改。

可以使用编译器优化来避免双重搜索,或者可以在恒定时间内减少搜索,并且可以进行编译器优化以避免将auto find变量存储在内存中(它可以是CPU寄存器,因为它的使用非常本地化。)

整个问题实际上会减少两次哈希计算两次的时间(并且在哈希冲突的情况下遍历最终的映射槽)以及访问额外变量的时间:

2*H < H+M

这意味着H < M。如果M是一个寄存器且H不是微不足道的,那么H很难小于M

答案 2 :(得分:8)

是的,它可能会更慢但可能可测量更慢。还有一些额外的工作:

  1. 哈希可能会计算两次,除非您有sufficiently smart compiler,使用pure or const等供应商扩展或使用类似的方法。请注意,如果散列是微不足道的,并且编译器知道它的代码,那么现在大多数编译器可能 足够聪明。
  2. 需要第二次找到存储桶的位置(除非编译器会注意到这是相同的哈希,因此它不需要重新计算)
  3. 需要执行碰撞列表的遍历(或根据碰撞解决方案的类似方法)。再一次 - 足够聪明的编译器可能会注意到我们正在做两次,我们实际上并没有修改任何东西等等。我们现在可能有这样的编译器但是我不能100%确定我们是否在那里。 即使它们不是高速缓存的读取,它们也可能不会产生任何显着的成本(例如,与哈希或错过读取相比)。没有详细讨论处理器架构L1 $读取命中在i7上需要大约4个周期的延迟(来自内存的数据,可能是错误的),处理器在等待它时可能会做其他工作。
  4. 总结如果:

    • 您的哈希函数很昂贵(例如,它需要获取字符串的哈希值)。
    • 编译器不够智能推断出哈希函数不会修改对象并确实返回相同的值。
    • 内循环中的代码。

    然后你可能会看到差异。


    最后一句话 - 它可能不重要,它不是很大的架构差异,而是5s优化。因此,当分析器显示此功能导致速度减慢时,请编写更容易维护的内容并重新查看问题。

答案 3 :(得分:2)

除非您有特定原因要保留现有条目中的值(如果已存在),否则您可以完全跳过第一个搜索,只需设置新值:

#include <unordered_map>

struct type { int member; };
std::unordered_map<key_t, type> map;

map[key].member = 42;

这将修改现有条目(如果有)并插入新条目(如果不存在)。

答案 4 :(得分:2)

是的,它可能会更慢。如果你正在寻找更具表现力的东西,也许你应该封装std:unordered_map(无论如何这可能是一个好主意)并暴露一个指针。然后你可以这样写:

auto thing = getThing(key);
if (thing) 
  thing->member = 42;