【问题标题】:Measuring a hash functions quality (for use with maps/assosiative arrays)测量哈希函数质量(用于映射/关联数组)
【发布时间】:2014-09-03 23:13:51
【问题描述】:

我正在研究 C 中的关联数组库(我没有编写)。类似于 C++ 或 Python 字典中的地图。

有一些非标准的散列函数,我不确定它们是否非常好。 (也许最初的开发者只是抛出了一些幻数、异或运算符并希望得到最好的结果)

我编写了一个测试,该测试测量哈希函数在给定一些样本输入的情况下的执行情况,以测量它如何均匀地将项目分配到固定数量的存储桶中(在本例中为模数组大小)。

这样,如果有足够的输入,就会有一些方法可以衡量哈希函数的执行情况。

对于任何编写关联数组的人来说,这似乎是一个常见问题。


是否有一些约定来衡量哈希函数的性能? (根据分发质量,而不是速度)

最坏的情况是每个输入的结果都相同,而最好的情况是均匀分布(或尽可能接近)。

注意,我不是在这里寻找加密强度。

【问题讨论】:

    标签: algorithm hash map


    【解决方案1】:

    龙之书上有一个Formula(在页面中间)。

    我个人有一个经验法则:(假设线性链接)将 N 个项目插入 N 个插槽->链中,并计算访问总数(链中的第一个:= 1 个访问;第二个:= 2 个访问等)需要获得所有 N 个元素。 (这等于 SUM (chainlen * (chainlen +1) /2),对所有链求和)

    给定随机输入数据,对于任何合理的散列函数,该指标应该是 1.5 * N,或者略低于该值。


    使用包含 2543846 个唯一标记/单词(及其统计信息)的列表的典型运行示例 精确地散列到 2543846 个插槽/桶中:

    plasser@pisbak:~/src/hash$ ./diskhash woorden.txt woorden.hsh
    Ptr = 0x7fb5c264f000, Sz = 37362821
    Array= 0x7fb5bff7e000 Cnt = 2543846
    __________________
    Histogram of seek lenghts:
    len:    Count     Hops   Fraction (Cumulative)
      1:  1606429  1606429 0.63149617 (0.63149617)
      2:   672469  1344938 0.26435130 (0.89584747)
      3:   205046   615138 0.08060472 (0.97645219)
      4:    48604   194416 0.01910650 (0.99555869)
      5:     9477    47385 0.00372546 (0.99928415)
      6:     1581     9486 0.00062150 (0.99990565)
      7:      215     1505 0.00008452 (0.99999017)
      8:       24      192 0.00000943 (0.99999961)
      9:        1        9 0.00000039 (1.00000000)
    Tot:  2543846  3819498           (1.50147)
    Cnt  2543846 Empty   937417 (0.36850) Collisions 247 RedDragon 7638996/7631537=1.000977
    __________________
    
    • 空槽的分数是 0.36850 ,这大约是它应该是的 (1/e)
    • 具有多个项目(链长度 > 1)的槽的比例也约为 (1/e)
    • 正好有 1 个项目的插槽的分数是剩下的 :: 1 - (2/e)
    • 冲突的数量似乎有点高,但 32 位哈希值上有 250 万个项目,这并不例外。

    【讨论】:

      【解决方案2】:

      一个有用的基准是可重复的随机性 - 我的意思是,将每个不同的键分配给随机选择的存储桶时的冲突倾向。

      例如,如果您在 1000 个桶表中有 500 个值,那么达到此基准意味着您在插入新值时发生冲突的几率为 0.5(无论它与现有元素相比如何)。

      如果您在哈希表中有一些工具,以便知道插入值时发生了多少冲突,那么您可以 - 例如 - 插入元素,直到达到特定的 size():buckets 比率(例如 0.5),然后循环插入一个元素(并累积一个碰撞计数器)然后删除另一个,直到你有一个好的样本量。然后,您可以将碰撞率与 0.5 进行对比,以了解散列函数的质量。或者,您的仪器可能允许您插入过多的值,然后遍历它们,询问必须跳过多少碰撞元素才能从元素散列到实际元素的桶中获取。您还可以在插入所有值期间测量冲突的数量,但是您必须考虑在重新调整哈希表大小之间发挥作用的size():buckets 比率范围。

      【讨论】:

      • 关于碰撞的可能性,单独测量碰撞不会检测到不平衡的结果(例如,如果一个桶有很多项目)。我目前使用的方法是与最好的情况进行比较(例如,将 30 个项目放入 10 个桶中将是每个桶中有 3 个项目),然后累积差异并报告。
      • @ideasman42:如果你想建立一个碰撞深度的直方图,然后再把自己击倒......再次 - 一个有效的随机放置是你的基准,你可以通过使用加密哈希轻松接近然后比较你的直方图。另外,并非所有哈希表实现都允许每个桶有多个项目 - 这是一个实现问题......在 C++ 的std::unordered_map 的情况下,它确实这样做了,您可以通过桶接口函数查看每个桶的 # 值 - 请参阅here
      • 问题的重点是我希望能够跳过构建直方图的步骤(我每次都必须循环并考虑)。而是测量散列函数的质量。尽管散列实现可能对什么被认为是好的散列函数有一些影响(我不知道,但我们假设它可以),粗略地说,返回不平衡分布的散列函数- 不是一个好的散列函数。这是可以衡量的。
      【解决方案3】:

      根据@wildplasser 的回答,这是来自Red Dragon Book 的方法,作为Python3.x 中的一个测试函数,以验证它是否按预期工作,结果如下:

      from random import random
      def hash_in_range(bucket_tot):
          # Simply return a random number to simulate a fairly even hash function:
          # using                   `random()`       -> `~1.0` score
          # replace `random()` with `random() * 0.5` -> `~2.0` score
          # replace `random()` with `random() * 0.1` -> `~10.0` score
      
          return int(random() * bucket_tot)
      
      def count_quality(bucket_items):
          sum = 0
          for count in bucket_items:
              sum += count * (count + 1)
          return (sum * len(bucket_items) / (items_count * (items_count + 2 * len(bucket_items) - 1)))
      
      
      def test(bucket_tot, items_count):
          bucket_items = [0] * bucket_tot
      
          for i in range(items_count):
              bucket = hash_in_range(bucket_tot)
              bucket_items[bucket] += 1
      
          print("Testing:", bucket_tot, items_count, end="\t")
          score = count_quality(bucket_items, items_count)
          print(score)
      
      
      test(100,       10000)
      test(1000,      100000)
      test(10000,     1000000)
      test(1000,      10000)
      test(1000,      100000)
      test(100,       1000000)
      test(1000,      1000000)
      test(500,       100000)
      test(25,        100000)
      

      还有示例 C 函数,其中存储桶是单链表指针数组。

      double hash_calc_quality(struct Hash *hash)
      {
          uint64_t sum = 0;
          unsigned int i;
      
          if (hash->nentries == 0)
              return -1.0;
      
          for (i = 0; i < hash->nbuckets; i++) {
              uint64_t count = 0;
              Entry *e;
              for (e = hash->buckets[i]; e; e = e->next) {
                  count += 1;
              }
              sum += count * (count + 1);
          }
          return ((double)sum * (double)hash->nbuckets /
                  ((double)hash->nentries * (hash->nentries + 2 * hash->nbuckets - 1)));
      }
      

      【讨论】:

      • python 是否以无限精度执行计算(最终公式中有一个正方形和一个立方体)? python 中的 random() 是否返回 int 或 float ? (devision 是否会缩小其可能值的范围?)
      • @wildplasser,不,不是无限精度,CPython3.x 在这种情况下将使用双精度。 random 返回一个双精度值,在这种情况下,它扩展到可用存储桶的范围并转换为 int。
      猜你喜欢
      • 1970-01-01
      • 2013-07-29
      • 1970-01-01
      • 2013-11-11
      • 2012-08-29
      • 1970-01-01
      • 2012-08-05
      • 1970-01-01
      • 2014-08-09
      相关资源
      最近更新 更多