存储为Int64的44位数字的GetHashCode()的最佳方法

时间:2014-09-25 18:08:13

标签: c# .net

我在Dictionary<MyKey, MyValue>中存储了大约5,000,000个对象。

MyKey是一个结构,它将我的密钥的每个组成部分(5个不同的数字)打包在Int64ulong)的最右边44位。

由于ulong将始终以20个零位开始,我的直觉是返回本机Int64.GetHashCode()实现可能会更频繁地发生冲突,而不是哈希代码实现只考虑44实际使用的位(虽然在数学上,我不知道从哪里开始证明理论)。

这会增加对.Equals()的调用次数,并使字典查找速度变慢。

Int64.GetHashCode()的.NET实现如下所示:

public override int GetHashCode()
{
    return (int)this ^ (int)(this >> 32);
}

我如何最好地实施GetHashCode()

2 个答案:

答案 0 :(得分:4)

我无法开始建议一种“最佳”方式来散列44位数字。但是,我可以建议一种方法将它与64位哈希算法进行比较。

这样做的一种方法是简单地检查你获得的一组数字的碰撞次数(正如McKenzie等人在Selecting a Hashing Algorithm中所建议的那样)除非你要测试你的所有可能的值,你需要判断你得到的碰撞数是否可以接受。这可以在代码中完成,例如:

var rand = new Random(42);
var dict64 = new Dictionary<int, int>();
var dict44 = new Dictionary<int, int>();
for (int i = 0; i < 100000; ++i)
{
    // get value between 0 and 0xfffffffffff (max 44-bit value)
    var value44 = (ulong)(rand.NextDouble() * 0x0FFFFFFFFFFF);
    var value64 = (ulong)(rand.NextDouble() * ulong.MaxValue);
    var hash64 = value64.GetHashCode();
    var hash44 = (int)value44 ^ (int)(value44>> 32);
    if (!dict64.ContainsValue(hash64))
    {
        dict64.Add(hash64,hash64);
    }
    if (!dict44.ContainsValue(hash44))
    {
        dict44.Add(hash44, hash44);
    }
}
Trace.WriteLine(string.Format("64-bit hash: {0}, 64-bit hash with 44-bit numbers {1}", dict64.Count, dict44.Count));

换句话说,始终生成100,000个随机64位值和100,000个随机44位值,对每个值执行散列并跟踪唯一值。

在我的测试中,这为44位数字生成了99998个唯一值,为64位数字生成了99997个唯一值。因此,对于44位数字而言,这是64位数字的 less 冲突。我希望与44位数字的冲突更少,因为你输入的可能性较小。

我不打算告诉你64位哈希方法对于44位是“最好的”;你必须决定这些结果是否意味着它对你的环境有利。

理想情况下,您应该使用应用程序可能生成的实际值进行测试。鉴于这些都是44位值,很难将其与碰撞ulong.GetHashCode()产生(即你的结果相同)。如果基于常量种子的随机值不够好,请用更好的方法修改代码。

虽然事情可能并没有“感觉”正确,但科学表明,如果没有可重复的测试来证明改变是必要的,那就没有必要改变。

答案 1 :(得分:-2)

这是我尝试回答这个问题,尽管答案与我的期望相反,但我仍在发帖。 (虽然我可能在某个地方犯了一个错误 - 我几乎都希望如此,并且对我的测试技术持批评态度。)

  // Number of Dictionary hash buckets found here:
  // http://stackoverflow.com/questions/24366444/how-many-hash-buckets-does-a-net-dictionary-use
  const int CNumberHashBuckets = 4999559;

  static void Main(string[] args)
  {
     Random randomNumberGenerator = new Random();

     int[] dictionaryBuckets1 = new int[CNumberHashBuckets];
     int[] dictionaryBuckets2 = new int[CNumberHashBuckets];

     for (int i = 0; i < 5000000; i++)
     {
        ulong randomKey = (ulong)(randomNumberGenerator.NextDouble() * 0x0FFFFFFFFFFF);

        int simpleHash = randomKey.GetHashCode();
        BumpHashBucket(dictionaryBuckets1, simpleHash);

        int superHash = ((int)(randomKey >> 12)).GetHashCode() ^ ((int)randomKey).GetHashCode();
        BumpHashBucket(dictionaryBuckets2, superHash);
     }

     int collisions1 = ComputeCollisions(dictionaryBuckets1);
     int collisions2 = ComputeCollisions(dictionaryBuckets2);
  }

  private static void BumpHashBucket(int[] dictionaryBuckets, int hashedKey)
  {
     int bucketIndex = (int)((uint)hashedKey % CNumberHashBuckets);
     dictionaryBuckets[bucketIndex]++;
  }

  private static int ComputeCollisions(int[] dictionaryBuckets)
  {
     int i = 0;
     foreach (int dictionaryBucket in dictionaryBuckets)
        i += Math.Max(dictionaryBucket - 1, 0);
     return i;
  }

我尝试模拟Dictionary完成的处理是如何工作的。 OP说他在字典中有“大约5,000,000”个对象,根据引用的来源,字典中将有4999559或5999471个“桶”。

然后我生成5,000,000个随机44位密钥来模拟OP的Dictionary条目,对于每个密钥,我用两种不同的方式哈希:简单的ulong.GetHashCode()和我在评论中建议的另一种方式。然后我使用modulo将每个哈希代码转换为存储桶索引 - 我假设它是由字典完成的。这用于增加伪桶作为计算冲突数量的方式。

不幸的是(对我而言)结果并不像我希望的那样。对于4999559个桶,模拟通常表示大约180万个冲突,我的“超级哈希”技术实际上有一些(大约0.01%)更多冲突。对于5999471个桶,通常有大约160万个冲突,而我所谓的超级哈希可以减少0.1%的冲突。

所以我的“直觉”是错误的,似乎没有理由试图找到更好的哈希码技术。