可怜的unordered_map插入性能/哈希函数

时间:2011-05-21 23:55:06

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

我现在一直在编写图像处理算法,在某些时候我需要收集一些有关转换像素的统计信息,以便更深入地了解我应该遵循的进一步开发方向。我需要收集的信息格式为:

key: RGB value
value: int

我做了什么,是我打开变换后的图像并通过它迭代,将我需要的值保存到具有以下签名的std::unordered_map

typedef std::unordered_map<boost::gil::rgb8_pixel_t, unsigned int> pixel_map_t;

循环:

for(int y = 0; y < vi.height(); y++) {
    SrcView::x_iterator dst_it = src.row_begin(y);
    for(int x = 0; x < vi.width(); x++, hits++) {
        diff_map.insert(std::make_pair(dst_it[x], /* some uint32 */));
    } 

我还编写了一个自定义哈希函数(它是一个完美的哈希函数:256^2 x R + 256 x G + B - 因此无论桶和哈希表的布局(合理的扩展),冲突都应该是最小的。

我注意到的是插入速度非常慢! - 在达到第11次迭代后,插入速度降低约100倍。我发生了大量的碰撞! 尽管图像中的重复颜色数量非​​常少。

之后,我想消除代码中的任何可能的错误,并开始使用STL哈希函数对unordered_map进行基准测试,例如int。

基准的代码是:

std::size_t hits = 0, colls = 0;
for(int y = 0; y < vi.height(); y++) {
    SrcView::x_iterator dst_it = src.row_begin(y);

    for(int x = 0; x < vi.width(); x++, hits++) {
        if(diff_map.find(x*y) != diff_map.cend())
            colls++;
        diff_map.insert(std::make_pair(x*y, 10));
    } 
    std::cout << y << "/" << vi.height() << " -> buckets: " 
              << diff_map.bucket_count() << "(" 
              << std::floor(diff_map.load_factor() * 100) 
              << "% load factor) [ " << colls << " collisions / " <<  hits << " hits ]"  << std::endl;
}

...以下是外部循环的前20次迭代的结果(仅对ST型键使用STL的散列函数):

0/480 -> buckets: 8(12% load factor) [ 639 collisions / 640 hits ]
1/480 -> buckets: 4096(15% load factor) [ 640 collisions / 1280 hits ]
2/480 -> buckets: 4096(23% load factor) [ 960 collisions / 1920 hits ]
3/480 -> buckets: 4096(31% load factor) [ 1281 collisions / 2560 hits ]
4/480 -> buckets: 4096(37% load factor) [ 1654 collisions / 3200 hits ]
5/480 -> buckets: 4096(45% load factor) [ 1964 collisions / 3840 hits ]
6/480 -> buckets: 4096(51% load factor) [ 2370 collisions / 4480 hits ]
7/480 -> buckets: 4096(59% load factor) [ 2674 collisions / 5120 hits ]
8/480 -> buckets: 4096(65% load factor) [ 3083 collisions / 5760 hits ]
9/480 -> buckets: 4096(71% load factor) [ 3460 collisions / 6400 hits ]
10/480 -> buckets: 4096(77% load factor) [ 3872 collisions / 7040 hits ]
11/480 -> buckets: 4096(85% load factor) [ 4161 collisions / 7680 hits ]
12/480 -> buckets: 4096(90% load factor) [ 4612 collisions / 8320 hits ]
13/480 -> buckets: 4096(99% load factor) [ 4901 collisions / 8960 hits ]
14/480 -> buckets: 32768(13% load factor) [ 5315 collisions / 9600 hits ]
15/480 -> buckets: 32768(13% load factor) [ 5719 collisions / 10240 hits ]
16/480 -> buckets: 32768(14% load factor) [ 6148 collisions / 10880 hits ]
17/480 -> buckets: 32768(15% load factor) [ 6420 collisions / 11520 hits ]
18/480 -> buckets: 32768(16% load factor) [ 6870 collisions / 12160 hits ]
19/480 -> buckets: 32768(17% load factor) [ 7135 collisions / 12800 hits ]
20/480 -> buckets: 32768(17% load factor) [ 7584 collisions / 13440 hits ]
21/480 -> buckets: 32768(18% load factor) [ 7993 collisions / 14080 hits ]

在这种情况下,碰撞次数是否过高? STL库通常具有高质量,但对于简单的基于int的键音具有639/640和640/1280至少是奇怪的。 或者也许我做错了什么?


这是我的哈希函数(理论上,应该没有碰撞 - 但数字非常接近):

template<> 
struct std::hash<boost::gil::rgb8_pixel_t> :
    public std::unary_function<const boost::gil::rgb8_pixel_t&, size_t>
{
    size_t operator()(const boost::gil::rgb8_pixel_t& key) const
    {
        size_t ret =  (static_cast<size_t>(key[0]) << 16) |
                      (static_cast<size_t>(key[1]) << 8) |
                      (static_cast<size_t>(key[2]));
        //return 256 * 256 * key[0] + 256 * key[1] + key[2];
        return ret;
    }
};

现在,这不再有趣......

我写了这个哈希函数:

template<> 
struct std::hash<int> :
    public std::unary_function<const int&, size_t>
{
    size_t operator()(const int& key) const
    {
        return 5;
    }
};

理论上,我应该有100%的碰撞率,对吗?但结果是:

0/480 -> buckets: 8(12% load factor) [ 639 collisions / 640 hits ]
1/480 -> buckets: 4096(15% load factor) [ 640 collisions / 1280 hits ]
2/480 -> buckets: 4096(23% load factor) [ 960 collisions / 1920 hits ]
3/480 -> buckets: 4096(31% load factor) [ 1281 collisions / 2560 hits ]
4/480 -> buckets: 4096(37% load factor) [ 1654 collisions / 3200 hits ]
5/480 -> buckets: 4096(45% load factor) [ 1964 collisions / 3840 hits ]
6/480 -> buckets: 4096(51% load factor) [ 2370 collisions / 4480 hits ]
7/480 -> buckets: 4096(59% load factor) [ 2674 collisions / 5120 hits ]
8/480 -> buckets: 4096(65% load factor) [ 3083 collisions / 5760 hits ]
9/480 -> buckets: 4096(71% load factor) [ 3460 collisions / 6400 hits ]

为什么?

环境:MSVS2010

5 个答案:

答案 0 :(得分:8)

colls不测量碰撞。如果您想测量碰撞,那么对于b范围内的每个广告资源[0, bucket_count()),请获取bucket_size(b)。这将告诉你每个桶中有多少项。如果广告素材中有2个或更多项目,那么您的广告资源bucket_size(b) - 1会发生b次冲突。

答案 1 :(得分:4)

您的哈希空间大小为24位。要进行0次碰撞,如果你的数据是完美的,那么你需要一个与数据大小相同的哈希表,否则需要大25到50%。我的猜测是你的哈希表比这个要小得多,因此容器会重新映射你的数据并导致冲突。

答案 2 :(得分:1)

如果我理解你正在做什么,你可能只是得到这些碰撞,因为图像中的许多像素具有相同的颜色,并且您反复调用diff_map.insert用于相同的颜色(因此哈希值的质量)是无关紧要的)。如果你这样做是为了计算颜色的直方图,你可能不想做“diff_map.insert(std :: make_pair(dst_it [x],/ * some uint32 * /));”,而只是做点什么

auto it = diff_map.find(dst_it [x]); if(it == diff_map.end())it = 1; else(it-&gt; second)++;

答案 3 :(得分:0)

  

我还编写了一个自定义哈希函数(它是一个完美的哈希函数:256 ^ 2 x R + 256 x G + B - 因此无论桶和哈希表的布局如何,冲突都应该是最小的(合理的延伸)。

这个哈希函数不好。一个好的哈希函数(当你不知道桶的数量时)应该为几乎相同的输入生成非常不同的哈希值。在您的情况下,一种非常简单的方法是使用三个256个随机32位值的表:int32_t rand[3][256] - 然后哈希ala rand[0][R] ^ rand[1][G] ^ rand[2][B]。这会在桶中随机分散您的值,而不会出现类似值的集群趋势:未知#桶的理想散列函数属性。

你也可以让库提供的哈希函数有一个破解,它们不可能改进哈希生成的随机表属性,但可能由于更少的内存查找而更快或者由于更复杂或更复杂的数学运算而变慢 - 如果你关心的话就是基准。

答案 4 :(得分:0)

即使您可能没有相同的值,值也可能足够接近。我很难找到时间序列或不分散的数字的良好散列函数。当unordered_map对具有桶数的散列值执行'%'(modulo)时,大多数值可能仅在几个桶中结束(如果散列值没有很好地分散)并且导致O(n)搜索

当哈希值不够分散时,我会使用std :: map(RB树),我得到O(log n)。