2

我正在寻找一种有效的方法来散列一个 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;
    }
};

我的问题有两个:

  1. 这些优化会导致 UB 吗?
  2. 我的第一个解决方案是要走的路吗?或者,还有更好的方法?
4

1 回答 1

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;
};

现在,您可以将其正确封装在一个类中(并确保始终初始化字节78to 0),并实现std::array<unsigned char, 6>您实际需要的接口。

我对小字符串(小于 8 个字符)使用了类似的技巧来进行散列和快速(非字母)比较,这真的很不错。

于 2012-05-12T14:08:11.423 回答