【问题标题】:Why is my std::unordered_map access time not constant为什么我的 std::unordered_map 访问时间不是恒定的
【发布时间】:2016-04-04 21:40:00
【问题描述】:

我编写了一些代码来测试我的无序地图性能,其中以 2 分量向量为键。

std::unordered_map<Vector2i, int> m;                                                                      

for(int i = 0; i < 1000; ++i)                                                                             
    for(int j = 0; j < 1000; ++j)                                                                         
        m[Vector2i(i,j)] = i*j+27*j;                                                                      

clock.restart();                                                                                          

auto found = m.find(Vector2i(0,5));                                                                                                                                                            

std::cout << clock.getElapsedTime().asMicroseconds() << std::endl;                                         

上述代码的输出:56(微秒) 当我将 for 循环中的 1000 替换为 100 时,输出为 2(微秒) 时间不应该是恒定的吗?

我的 Vector2i 的散列函数:

namespace std                                                                                                    
{

   template<>                                                                                                   
    struct hash<Vector2i>                                                                                        
    {                                                                                                            
        std::size_t operator()(const Vector2i& k) const                                                          
        {                                                                                                        
            using std::size_t;                                                                                   
            using std::hash;                                                                                     
            using std::string;                                                                                   

            return (hash<int>()(k.x)) ^ (hash<int>()(k.y) << 1);                                                 
        }                                                                                                        

    };                                                                                                           


}                                                                             

编辑: 我添加了这段代码来计算 for 循环后的冲突:

for (size_t bucket = 0; bucket != m.bucket_count(); ++bucket)                                             
    if (m.bucket_size(bucket) > 1)                                                                        
         ++collisions; 

使用 100*100 个元素:碰撞次数 = 256

1000*1000 个元素:碰撞次数 = 2048

【问题讨论】:

  • 如果你有很多哈希冲突,你的哈希映射会退化成一个链表,也许就是这样。
  • 尝试为您的类型添加一些填充,使其占据整个缓存行。在小尺寸下,缓存位置可能是主要因素。
  • @aqww 是的,这本身就是一门艺术;)This (and links within) 可能有用。
  • 您使用的是哪个编译器? Visual C++ 使用 2 次幂的存储桶计数,使用如此弱的散列函数会更糟,而 GCC 使用素数。有碰撞的桶的数量并不是最有意义的计算——这些碰撞链有多深是非常重要的。而且,您只对单个查找进行计时,因此对于您当前的基准测试,只有 bucket_sizem.bucket(Vector2i(0,5)) 相关。不管怎样,试试boost::hash_combinealgo therefrom - 会有帮助的。
  • @TonyD 我使用 GCC 编译器。你是对的,我的碰撞计数不好,更好的方法是每个桶的碰撞 += bucket_size - 1。答案所建议的 hash_combine 解决了我的问题。 (现在我的时间几乎是固定的)

标签: c++ c++11 hashtable time-complexity unordered-map


【解决方案1】:

哈希表保证constant amortized time。如果散列表平衡良好(即散列函数良好),则大多数元素将均匀分布。但是,如果哈希函数不太好,您可能会遇到很多冲突,在这种情况下,要访问一个元素,您通常需要遍历一个链表(存储发生冲突的元素的地方)。所以首先确保load factor 和散列函数在你的情况下是可以的。最后,确保在发布模式下编译代码并启用优化(例如,-O3 用于 g++/clang++)。

这个问题也可能有用:How to create a good hash_combine with 64 bit output (inspired by boost::hash_combine)

【讨论】:

  • 2 和 56 微秒也不能用作可靠的度量。在这个数量级的时序下,操作系统和硬件中的噪声将占主导地位。
  • @bolov 没错,应该测试相对较大的时间/数量的元素,并适当地缩放它们。虽然 if 可能会给您暗示某些事情可能是可疑的。
  • 我实现了 hash_combine 的东西,现在我有 1 微秒的 100*100 元素,3 微秒的 1000*1000。但是碰撞数有问题......它为我输出 258409 个 1000 循环的碰撞,2192 个 100 循环的碰撞.. 我认为我没有按应有的方式计算它们..
  • @aqww 时序缩放看起来好多了,虽然对于那些超小的时间无法得出明确的结论,请参阅 bolov 的评论。尝试将元素数量增加 1000 倍并测试更长的时间。尝试使用std::unordered_map::bucket_size 查看每个桶有多少元素(即碰撞)。
  • @AnthonyAkentiev 对不起,我的意思是只有 1 个元素的桶,不是空的。虽然我同意你的看法(并且在我的回答中也提到了负载系数),但总的来说这是一个很好的指标。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-01-17
  • 2011-08-18
  • 1970-01-01
  • 2011-09-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多