【问题标题】:hash function for src dest ip + portsrc dest ip + port 的哈希函数
【发布时间】:2011-03-14 00:21:31
【问题描述】:

因此,我正在研究用于散列 4 元组 ip 和端口以识别流的不同散列函数。

我遇到的一个是

((size_t)(key.src.s_addr) * 59) ^
((size_t)(key.dst.s_addr)) ^
((size_t)(key.sport) << 16) ^
((size_t)(key.dport)) ^
((size_t)(key.proto));

对于我的一生,我无法解释所使用的素数 (59)。为什么不是 31,然后为什么要通过将这项运动乘以 2 的幂来搞砸它。 是否有更好的哈希函数用于 ip 地址?

【问题讨论】:

    标签: c hash networking hashtable


    【解决方案1】:

    之所以使用素数,是因为当一个值乘以一个素数时,当在其上累积其他类似操作时,它往往具有更高的保持唯一性的概率。特定值 59 可能是任意选择的,也可能是有意的。很难说。 59 可能会根据最可能的输入生成更好的值分布。

    移位 16 可能是因为端口限制在 2^16 范围内。该功能似乎将源端口移动到位域的较高部分,而将目标端口留在较低部分。我认为这可以在我的下一段中进一步解释。

    发生乘法的另一个原因(移位操作也是如此)是因为它破坏了散列函数的关联性质。请记住,XOR 是关联的,因此如果乘法不存在,IP src=192.168.1.1 和 dst=192.168.1.254 将散列到与 src=192.168.1.254 和 dst=192.168.1.1 相同的值(交换)。

    【讨论】:

    • 那么,为什么不将每个 ip 与素数相乘而不仅仅是源。
    • 哈希函数将再次变为关联函数,因为(a*P)^(b*P) = (b*P)^(a*P)。虽然经常看到两个互质数协同工作以做类似的事情。
    【解决方案2】:

    我个人认为你最好将四个 IP 字节读取为无符号长整数,这会给你一个大致在 0 - 2^32-1 范围内的数字。然后,您可以计算出您希望在任何时候激活多少流,这就是您的索引表大小。

    以 2000 为例。这意味着您希望将 2^32 个数字映射到大约 2^11 个整数上(以流动信息)。那是行不通的,因为如果填充到 100% 甚至 90%,散列几乎永远不会起作用。使用仅填充到 50%(4000 个 indeces)甚至 25%(8000)的索引表对于今天的记忆来说没什么大不了的。

    索引表的确切大小应该是奇数个位置,最好是素数。这是因为您很可能需要进行一些溢出处理来处理冲突(在散列后指向索引表中相同位置的两个或多个 ip 号) - 您将得到。溢出处理应该是另一个小于索引表大小的素数。所有这些质数!他们到底怎么了?

    我将用一个例子来说明(用 C 语言):

    idxsiz = prime(2000 * 2);    // 50% loading
    ovfjmp = prime(idxsiz/3);
    

    ...

    最初用 UNUSED 标记 (-1) 填充 idxjmp 位置表。准备好 DELETED 标记 (-2)。

    你的ip号进入系统,你寻找它的流水记录(可能存在也可能不存在):

    stoppos = ip % idxsiz;    /* modulo (division) just this once */
    i = stoppos;
    do
    {
      if (index[i] == UNUSED) return NULL;
      if (index[i] != DELETED)
      {
        flowrecptr = &flow_record[index[i]];
        if (!flowrecptr->in_use) {/* hash table is broken */}
        if (flowrecptr->ip == ip) return flowrecptr;
      }
      i += ovfjmp;
      if (i >= idxsiz) i -= idxsiz;
    }
    while (i != stoppos);
    return NULL; 
    

    UNUSED 用作该索引从未被使用过并且应该停止搜索的标记。 DELETED 用作该索引已使用但不再使用的标记。这意味着搜索必须继续。

    这是您尝试获取的时候。你从 get 得到一个 NULL,所以你需要做一个 put,首先找到包含 UNUSED 或 DELETED 的第一个索引位置。将此值替换为 flow_record 表上第一个/下一个空闲行的索引。将该行标记为 in_use。将原始ip号放入flow_record行的ip成员中。

    这是构造散列机制的一种非常简单但非常有效的方法。实际上,在这个或那个函数失败后要使用的特殊函数形式的每一个优化都会提高散列的有效性。

    使用素数将确保 - 在所有索引位置都被占用的最坏情况下 - 该机制将测试每个位置。为了说明这一点:假设 idxsiz 可以被 ovfjmp 整除:您不会有太多溢出处理可言。 35 和 7 将导致在索引跳转到 0 之前测试位置 0、7、14、21 和 28,而 while 测试将导致搜索停止。

    ----------糟糕!

    我错过了您也想要端口号。假设 ip V4 意味着 6 个字节的地址。将其读取为无符号 64 位整数并清除前 16 位/2 个字节。然后进行模数计算。

    【讨论】:

      【解决方案3】:

      检查函数的输出是否均匀分布。如果您不喜欢它,请插入一些不同的素数,直到获得您喜欢的分布。散列可能是一门非常黑暗的艺术,没有“正确”答案。

      【讨论】:

      • 是的。最有可能的是,这个特定的函数是由于在测试数据上测试了许多不同的函数并查看了哪些提供了最佳行为。
      【解决方案4】:

      Brian Gideon 总结得很到位;乘法和移位旨在破坏对称性。所以这抓住了机器 A 远程登录到机器 B 的假设情况,反之亦然,他们碰巧选择了相同的临时端口号。不是很常见,但并非不可能。 5 元组的大部分内容是相当恒定的:协议来自一个非常小的域,{address,portnum} 的一半也是如此。

      假设一个素数大小的哈希表,魔法常数 59 可以被任何素数替换,IMO。 (port

      对于大小为 2 的哈希表,5 元组的所有(减一)成员应乘以(不同的)素数。 (在过去,当分裂很昂贵时,这是一种选择)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-09-01
        • 2011-01-24
        • 1970-01-01
        • 1970-01-01
        • 2016-03-25
        • 2011-02-27
        相关资源
        最近更新 更多