我正在寻找一种有效的方法来对6字节字段进行散列,以便它可以用于std::unordered_map
。
我认为这将是创建哈希的传统方式:
struct Hash {
std::size_t operator()(const std::array<uint8_t, 6> & mac) const {
std::size_t key = 0;
boost::hash_combine(key, mac[0]);
boost::hash_combine(key, mac[1]);
boost::hash_combine(key, mac[2]);
boost::hash_combine(key, mac[3]);
boost::hash_combine(key, mac[4]);
boost::hash_combine(key, mac[5]);
return key;
}
};
但是我注意到我可以使用这个技巧让它快一点(~20%):
struct Hash {
std::size_t operator()(const std::array<uint8_t, 6> & mac) const {
std::size_t key = 0;
// Possibly UB?
boost::hash_combine(key, reinterpret_cast<const uint32_t&>(mac[0]));
boost::hash_combine(key, reinterpret_cast<const uint16_t&>(mac[4]));
return key;
}
};
这更快:
struct Hash {
std::size_t operator()(const std::array<uint8_t, 6> & mac) const {
// Requires size_t to be 64-bit.
static_assert(sizeof(std::size_t) >= 6, "MAC address doesn't fit in std::size_t!");
std::size_t key = 0;
// Likely UB?
boost::hash_combine(key, 0x0000FFFFFFFFFFFF & reinterpret_cast<const uint64_t&>(mac[0]));
return key;
}
};
我的问题是双重的:
答案 0 :(得分:3)
您的优化打破了严格的别名规则,这导致(标准地说)未定义的行为。
最后一次优化让我最担心,因为你本质上是在阅读你不应该的内存,如果这个内存碰巧受到保护,可能会引发陷阱。
您没有使用boost::hash_range
的任何原因?
由于boost::hash_range
的结果并不像要求的那么快,我会提出另一种基于别名的解决方案。或者更确切地说,两种解决方案合二为一。
第一个想法是可以使用char*
作为临时类型来抑制别名。
size_t key = 0;
char* k = &reinterpret_cast<char*>(&key);
std::copy(mac.begin(), mac.end(), k);
return key;
因此,是散列的有效实现。
然而,我们可以更进一步。由于对齐和填充,存储char[6]
和char[8]
可能会在地图节点中使用相同数量的内存。因此,我们可以使用union
:
union MacType {
unsigned char value[8];
size_t hash;
};
现在,您可以在类中正确封装(并确保始终将字节7
和8
初始化为0
),并实现{{1}的接口你实际需要的。
我对小字符串(低于8个字符)使用类似的技巧进行散列和快速(非字母)比较,这真的很棒。