可怜的unordered_map插入性能/哈希函数
Miserable unordered_map insertion performance / hash function
我现在一直在编写图像处理算法,在某些时候我需要收集一些关于转换像素的统计信息,以更深入地了解我应该遵循的进一步发展方向。我需要收集的信息类型是以下格式:
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 次迭代的结果(仅使用 STL 的哈希函数进行 int 类型键):
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
colls
没有测量碰撞。 如果要测量冲突,则对于[0, bucket_count())
范围内的每个存储桶b
,请获取bucket_size(b)
。 这将告诉您每个存储桶中有多少个项目。 如果存储桶中有 2 个或更多项目,则存储桶b
的冲突bucket_size(b) - 1
。
您的哈希空间大小为 24 位。 要有 0 次冲突,如果您的 have 是完美的,您需要一个与数据大小相同的哈希表,如果不是,则需要大 25-50%。 我的猜测是你已经使你的哈希表比这小得多,因此容器正在重新映射你的数据并导致冲突。
如果我理解您正在正确执行的操作,您可能只是遇到这些冲突,因为图像中的许多像素具有相同的颜色,并且您反复调用 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;否则(它>秒)++;
我还编写了一个自定义哈希函数(这是一个完美的哈希函数: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]
。 这会将您的值随机分散到存储桶中,而没有针对相似值进行聚类的趋势:未知 # 存储桶的理想哈希函数属性。
你也可以让库提供的哈希函数有一个裂缝,它们不可能改进哈希生成的随机表属性,但由于内存查找较少,可能会更快,或者由于更多或更复杂的数学运算而变慢 - 如果你关心的话,基准测试。
即使值可能不相等,但这些值可能足够接近。我很难为不分散的时间序列或数字找到好的哈希函数。当unordered_map对带有存储桶数量的哈希值执行"%"(模数)时,您的大多数值可能仅出现在少数存储桶中(如果哈希值不太分散),这会导致 O(n) 搜索。
当哈希值不够分散时,我只会使用 std::map(RB 树),在那里我得到 O(log n)。
- 删除一个线程上有数百万个字符串的大型哈希映射会影响另一个线程的性能
- 有没有一种方法可以创建一个带有哈希表的数据库,该哈希表具有恒定时间查找功能
- 使用Qt C++计算类似Git的SHA1哈希
- 如何将这个C++哈希表转换为动态扩展和收缩,而不是使用硬设置的最大值
- 用C++将哈希表写入文件并从文件中恢复
- c++找不到具有相同哈希的无序集合元素
- 哈希文件递归并保存到矢量Cryptopp中
- 对 pair<pair<int,int>pair<int,int unordered_map进行哈希处理>>
- 直接在 unordered_map 的方法中使用哈希,而不是生成哈希的用户定义对象
- 如何为字符串生成唯一但一致的 N 位哈希(小于 64 位)?
- 使用对象的基类部分模板专用化对对象进行哈希处理::哈希
- boost::包含提升单元的元组的哈希值
- 使用 Key 对 C++ 中的哈希映射进行排序. 无法排序
- C++中的并发哈希表
- 哈希映射使用 nullptr c++ 初始化节点的动态数组
- 标准库容器最简单、性能差的哈希类是什么?
- 字符串与哈希作为映射键 - 性能
- 如何提高具有100万个元素和997个存储桶的哈希表的性能
- STL哈希表[无序映射]查找性能与大小
- 可怜的unordered_map插入性能/哈希函数