我实现了这个解决方案,用于从vector<T>
获取哈希值:
namespace std
{
template<typename T>
struct hash<vector<T>>
{
typedef vector<T> argument_type;
typedef std::size_t result_type;
result_type operator()(argument_type const& in) const
{
size_t size = in.size();
size_t seed = 0;
for (size_t i = 0; i < size; i++)
//Combine the hash of the current vector with the hashes of the previous ones
hash_combine(seed, in[i]);
return seed;
}
};
}
//using boost::hash_combine
template <class T>
inline void hash_combine(std::size_t& seed, T const& v)
{
seed ^= std::hash<T>()(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}
但是这个解决方案根本没有扩展:使用1000万个vector<double>
元素,它需要超过2.5秒(根据VS)。
这种情况是否存在快速哈希函数?
请注意,从向量引用创建哈希值不是一个可行的解决方案,因为相关的unordred_map
将在不同的运行中使用,此外两个vector<double>
具有相同的内容但不同的地址将映射不同(此应用程序的不良行为)。
答案 0 :(得分:9)
注意: As per the comments,您可以通过优化编译获得25-50倍的加速。首先,这样做。 然后,如果它仍然太慢,请参见下文。
我认为你无能为力。你有来触摸所有元素,而且这个组合函数的速度和它一样快。
一个选项可能是并行化散列函数。如果你有8个核心,你可以运行8个线程到每个散列1/8的向量,然后在最后组合8个结果值。对于非常大的向量,同步开销可能是值得的。
答案 1 :(得分:6)
MSVC旧的hashmap使用的方法是不经常采样。
这意味着隔离的更改不会显示在您的哈希中,但您要避免的是读取和处理整个80 MB的数据以便对您的向量进行哈希处理。不读一些字是非常不可避免的。
你应该做的第二件事是并非专门std::hash
所有vector
,这可能会使你的程序格式不正确(正如缺陷解决方案所建议的那样)我不记得了,至少是一个糟糕的计划(因为std
肯定允许自己添加散列组合和散列矢量)。
当我编写自定义哈希时,我通常使用ADL(Koenig Lookup)来扩展它。
namespace my_utils {
namespace hash_impl {
namespace details {
namespace adl {
template<class T>
std::size_t hash(T const& t) {
return std::hash<T>{}(t);
}
}
template<class T>
std::size_t hasher(T const& t) {
using adl::hash;
return hash(t);
}
}
struct hash_tag {};
template<class T>
std::size_t hash(hash_tag, T const& t) {
return details::hasher(t);
}
template<class T>
std::size_t hash_combine(hash_tag, std::size_t seed, T const& t) {
seed ^= hash(t) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}
template<class Container>
std::size_t fash_hash_random_container(hash_tag, Container const& c ) {
std::size_t size = c.size();
std::size_t stride = 1 + size/10;
std::size_t r = hash(hash_tag{}, size);
for(std::size_t i = 0; i < size; i += stride) {
r = hash_combine(hash_tag{}, r, c.data()[i])
}
return r;
}
// std specializations go here:
template<class T, class A>
std::size_t hash(hash_tag, std::vector<T,A> const& v) {
return fash_hash_random_container(hash_tag{}, v);
}
template<class T, std::size_t N>
std::size_t hash(hash_tag, std::array<T,N> const& a) {
return fash_hash_random_container(hash_tag{}, a);
}
// etc
}
struct my_hasher {
template<class T>
std::size_t operator()(T const& t)const {
return hash_impl::hash(hash_impl::hash_tag{}, t);
}
};
}
现在my_hasher
是一个普遍的哈希。它使用在my_utils::hash_impl
(对于std
类型)中声明的散列,或者将散列给定类型的名为hash
的自由函数来散列事物。如果失败,它会尝试使用std::hash<T>
。如果失败,则会出现编译时错误。
在您想要哈希的类型的命名空间中编写一个免费的hash
函数比在我的经验中不得不关闭并打开std
并专门化std::hash
更不烦人。
它以递归方式理解向量和数组。做元组和对子需要更多的工作。
它对所述载体和阵列进行约10次采样。
(注意:hash_tag
既是一个笑话,又是一种强制ADL并防止必须在hash
命名空间中转发声明hash_impl
特化的方法,因为要求很糟糕。)
抽样的价格是你可能会遇到更多的碰撞。
如果您拥有大量数据,另一种方法是将它们哈希一次,并跟踪它们何时被修改。要执行此方法,请为您的类型使用copy-on-write monad接口,以跟踪散列是否是最新的。现在一个矢量被哈希一次;如果你修改它,哈希就会被丢弃。
可以进一步使用随机访问哈希(可以很容易地预测当您以哈希方式编辑给定值时会发生什么),并调解对向量的所有访问。这很棘手。
你也可以对哈希进行多线程处理,但是我猜你的代码可能是内存带宽限制的,多线程也无济于事。值得一试。
您可以使用比平面向量(类似于树)更高级的结构,其中值的更改以类似哈希的方式冒泡到根哈希值。这将为所有元素访问添加lg(n)开销。同样,您必须将原始数据包装在控件中,以使哈希保持最新(或者,跟踪哪些范围是脏的并且需要更新)。
最后,因为您一次只使用1000万个元素,所以请考虑转移到强大的大型存储解决方案,例如数据库或您拥有的内容。在地图中使用80兆字节的密钥对我来说很奇怪。