【问题标题】:What's the best hashing algorithm to use on a stl string when using hash_map?使用 hash_map 时在 stl 字符串上使用的最佳散列算法是什么?
【发布时间】:2010-09-11 00:29:48
【问题描述】:

我发现 VS2005 上的标准散列函数在尝试实现高性能查找时非常缓慢。有哪些快速高效的散列算法可以避免大多数冲突的好例子?

【问题讨论】:

标签: c++ windows performance stl hash


【解决方案1】:

这始终取决于您的数据集。

通过使用字符串的 CRC32,我得到了令人惊讶的好结果。适用于各种不同的输入集。

在网上很容易找到很多好的 CRC32 实现。

编辑:差点忘了:这个页面有一个很好的哈希函数枪战,带有性能数字和测试数据:

http://smallcode.weblogs.us/

【讨论】:

    【解决方案2】:

    Boost 有一个boost::hash 库,它可以为大多数常见类型提供一些基本的哈希函数。

    【讨论】:

      【解决方案3】:

      来自我的一些旧代码:

      /* magic numbers from http://www.isthe.com/chongo/tech/comp/fnv/ */
      static const size_t InitialFNV = 2166136261U;
      static const size_t FNVMultiple = 16777619;
      
      /* Fowler / Noll / Vo (FNV) Hash */
      size_t myhash(const string &s)
      {
          size_t hash = InitialFNV;
          for(size_t i = 0; i < s.length(); i++)
          {
              hash = hash ^ (s[i]);       /* xor  the low 8 bits */
              hash = hash * FNVMultiple;  /* multiply by the magic number */
          }
          return hash;
      }
      

      它很快。真的是太快了。

      【讨论】:

      • 它可能很快,但它可能是有史以来发明的最糟糕的哈希函数之一。
      • @Matthieu:为什么?许多重复?你有什么参考资料可以让我了解更多吗?
      • @Albert: ^ 是可传递的,这很糟糕。 FNVMultiple 不是素数,这很糟糕。 InitialFNV 也不是素数,这可能是坏的,也可能不是坏的,我不确定。
      • @MooingDuck FNVMultiple 似乎是一个素数。
      • 16777619 是一个(经过验证的)素数。 2166136261 是(经过验证的)复合材料(失败的 sprp 测试基础 2)。 primes.utm.edu/curios/includes/primetest.php
      【解决方案4】:

      字符串哈希的一个经典建议是逐个遍历字母,将它们的 ascii/unicode 值添加到累加器中,每次将累加器乘以素数。 (允许哈希值溢出)

        template <> struct myhash{};
      
        template <> struct myhash<string>
          {
          size_t operator()(string &to_hash) const
            {
            const char * in = to_hash.c_str();
            size_t out=0;
            while(NULL != *in)
              {
              out*= 53; //just a prime number
              out+= *in;
              ++in;
              }
            return out;
            }
          };
      
        hash_map<string, int, myhash<string> > my_hash_map;
      

      如果不丢弃数据,很难获得比这更快的速度。如果您知道您的字符串只能通过几个字符而不是它们的全部内容来区分,那么您可以做得更快。

      如果值的计算过于频繁,您可以尝试通过创建一个新的 basic_string 子类来更好地缓存哈希值,以记住其哈希值。不过,hash_map 应该在内部进行。

      【讨论】:

      • 尤达状态警报!除此之外,这类似于 Larson 算法(我注意到这是之前发布的!)。
      【解决方案5】:

      我已经使用 Jenkins 哈希编写了一个 Bloom 过滤器库,它具有出色的性能。

      详细信息和代码可在此处获得:http://burtleburtle.net/bob/c/lookup3.c

      这是 Perl 用于其散列操作的方法,fwiw。

      【讨论】:

      • 另请参阅spooky hash,这是对 Jenkins 的改进
      【解决方案6】:

      如果您对一组固定的单词进行散列,最好的散列函数通常是perfect hash function。但是,它们通常要求您尝试散列的单词集在编译时是已知的。检测lexer 中的关键字(并将关键字转换为标记)是使用gperf 等工具生成的完美哈希函数的常见用法。完美的哈希还可以让您将hash_map 替换为简单的数组或vector

      如果您没有对一组固定的单词进行散列,那么显然这不适用。

      【讨论】:

        【解决方案7】:

        如果您的字符串平均比单个缓存行长,但它们的长度+前缀相当独特,请考虑仅包含长度+前 8/16 个字符。 (长度包含在 std::string 对象本身中,因此读取成本低)

        【讨论】:

          【解决方案8】:

          我与 Microsoft Research 的 Paul Larson 合作开发了一些哈希表实现。他在各种数据集上研究了许多字符串散列函数,发现简单的乘以 101 和加法循环的效果出奇的好。

          unsigned int
          hash(
              const char* s,
              unsigned int seed = 0)
          {
              unsigned int hash = seed;
              while (*s)
              {
                  hash = hash * 101  +  *s++;
              }
              return hash;
          }
          

          【讨论】:

          • 嘿乔治。我尝试了我在答案中发布的哈希基准中的代码。很好的发现。它在性能或碰撞方面并不出色,但它总是给出一致的结果。似乎它是通用字符串散列的一个又好又便宜的候选者。
          • 但这仅适用于小长度字符串。对于大型案例,它在大多数情况下都会溢出。
          • Soumajyoti,溢出无关紧要。大多数哈希函数溢出。关键是您在低位 32 位中获得了不错的位组合。
          • 这类似于 Java 实现,但它使用 31 而不是 101。
          • 如果我们已经计算了给定字符串的所有前缀的哈希值,我们如何找到子字符串的哈希值?例如,考虑s = "hello",s的所有前缀的hash为:[104, 10605, 1071213, 108192621, 2337520240]如何快速找到子串hel的hash?
          【解决方案9】:

          我做了一些搜索,有趣的是,Paul Larson 的小算法出现在这里 http://www.strchr.com/hash_functions 在多种条件下测试的碰撞最少,而且对于展开或桌面驱动的测试来说,它的速度非常快。

          Larson 是简单的乘以 101 并在上面添加循环。

          【讨论】:

            【解决方案10】:

            Python 3.4 包含一个基于SipHash 的新哈希算法。 PEP 456 信息量很大。

            【讨论】:

            • 我运行了一些benchmarks,SipHash 看起来很不错
            【解决方案11】:

            来自Hash Functions all the way down

            MurmurHash 作为“通用哈希函数”非常流行,至少在游戏开发者圈子里如此。

            这是一个不错的选择,但让我们稍后看看我们是否可以做得更好。另一个不错的选择,特别是如果您对数据的了解比“它将是未知的字节数”更多,是滚动您自己的(例如,参见 Won Chun 的回复,或 Rune 修改后的 xxHash/Murmur,它们专门用于 4 字节密钥等等。)。如果您知道自己的数据,请始终尝试看看这些知识是否可以用于产生良好的效果!

            如果没有更多信息,我会推荐MurmurHash 作为通用non-cryptographic hash function。对于小字符串(程序中的平均标识符大小),非常简单且著名的djb2FNV 非常好。

            在这里(数据大小

            djb2

            unsigned long
            hash(unsigned char *str)
            {
                unsigned long hash = 5381;
                int c;
            
                while (c = *str++)
                    hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
            
                return hash;
            }
            

            FNV-1

            hash = FNV_offset_basis
            for each byte_of_data to be hashed
                 hash = hash × FNV_prime
                 hash = hash XOR byte_of_data
            return hash
            

            FNV-1A

            hash = FNV_offset_basis
            for each byte_of_data to be hashed
                 hash = hash XOR byte_of_data
                 hash = hash × FNV_prime
            return hash
            

            关于安全性和可用性的说明

            哈希函数会使您的代码容易受到拒绝服务攻击。如果攻击者能够强制您的服务器处理过多的冲突,您的服务器可能无法处理请求。

            MurmurHash 这样的一些哈希函数接受一个种子,您可以提供该种子以大大降低攻击者预测您的服务器软件正在生成的哈希值的能力。请记住这一点。

            【讨论】:

            • @FelixSFD 我刚刚改进了答案。
            猜你喜欢
            • 2015-05-02
            • 1970-01-01
            • 1970-01-01
            • 2010-10-31
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2017-10-31
            相关资源
            最近更新 更多