是因为两个查找而不是一个查找,所以速度变慢了吗



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

#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;

第一个选项感觉更有表现力。真的慢了吗?

它可能会更慢,也可能不会(你现在正在"加速"中进行额外的写入),但当编写代码时,真的不应该担心这些小的优化。编写清晰的表达代码。然后,如果你的程序真的太慢了,就在上面运行评测工具,找到你的瓶颈。如果这个代码实际上是真正的问题,那么,只有这样,才能尝试"加速",看看它是否重要。

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

这就像打开一个抽屉,看看是否有给定的物体,说"啊,是的!"然后关上抽屉,然后再次打开,研究物体来改变它。

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

可以有编译器优化来避免双重搜索,或者可以在恒定时间内减少搜索,也可以有编译器最优化来避免auto find变量存储在内存上(它可以是CPU寄存器,因为它的使用非常局部)。

整个问题实际上将减少两次哈希计算的时间(在哈希冲突的情况下,走到最终的映射槽)和访问额外变量的时间:

2*H < H+M

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

是的,它可能会慢一些,但可能不会慢一些。还有一些额外的工作要做:

  1. 散列可能会被计算两次,除非你有足够聪明的编译器,使用供应商扩展名,如pure或const,或者使用类似的方法。请注意,如果hash是琐碎的,并且编译器知道它是代码,那么现在大多数编译器可能都足够聪明了
  2. bucket的位置需要第二次找到(除非编译器注意到这是同一个散列,所以不需要重新计算)
  3. 需要执行冲突列表的遍历(或取决于冲突解决方案的类似方法)。同样,足够聪明的编译器可能会注意到我们做了两次,实际上我们没有修改任何东西等等。我们现在可能有这样的编译器,但我不能100%确定我们是否在那里。即使它们不是缓存读取,它们也可能不会带来任何显著的成本(例如,与哈希或漏读相比)。不详细介绍处理器架构L1$read hit在i7上需要大约4个周期的延迟(来自内存的数据可能是错误的),处理器可能会在等待它的同时做其他工作

所以总结一下如果:

  • 您的散列函数非常昂贵(例如,它需要获取字符串的散列)
  • 编译器不够聪明,无法推断哈希函数不会修改对象,并且确实返回相同的值
  • 这是内部循环中的代码

那么您可能会看到差异。


最后一句话——这可能无关紧要,这不是很大的架构差异,而是5s优化。因此,编写您认为更容易维护的内容,并在探查器显示此函数会导致速度减慢时重新讨论这个问题。

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

#include <unordered_map>
struct type { int member; };
std::unordered_map<key_t, type> map;
map[key].member = 42;

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

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

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

最新更新