簡體   English   中英

存儲為Int64的44位數字的GetHashCode()的最佳方法

[英]Best way to GetHashCode() for 44-bit number stored as Int64

我有大約5,000,000個對象存儲在Dictionary<MyKey, MyValue>

MyKey是一個結構,它將我的密鑰的每個組件(5個不同的數字)打包在Int64ulong )的最右邊44位中。

由於ulong將始終以20個零位開始,我的直覺是返回本機Int64.GetHashCode()實現可能會更頻繁地發生沖突,而不是哈希代碼實現只考慮實際使用的44位(雖然在數學上,我不知道從哪里開始證明這個理論)。

這會增加對.Equals()的調用次數,並使字典查找速度變慢。

Int64.GetHashCode()的.NET實現如下所示:

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

我如何才能最好地實現GetHashCode()

我無法開始建議一種“最佳”方式來散列44位數字。 但是,我可以建議一種方法來將它與64位哈希算法進行比較。

一種方法是簡單地檢查你獲得的一組數字的碰撞次數(正如McKenzie等人在選擇哈希算法中所建議的那樣)除非你要測試你的所有可能的值,否則你需要判斷你得到的碰撞數是否可以接受。 這可以在代碼中完成,例如:

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個唯一值。 所以,這是超過64位數字的44位數字了一個碰撞。 我希望與44位數字的沖突更少,因為你輸入的可能性較小。

我不打算告訴你64位哈希方法對於44位是“最好的”; 你必須決定這些結果是否意味着它對你的環境有好處。

理想情況下,您應該使用應用程序可能生成的實際值進行測試。 鑒於這些都是44位值,很難將它與ulong.GetHashCode()產生的碰撞進行比較(即你的結果相同)。 如果基於常量種子的隨機值不夠好,請使用更好的方法修改代碼。

雖然事情可能沒有“感覺”正確,但科學表明,如果沒有可重復的測試來證明改變是必要的,那就沒有必要改變一些東西。

這是我嘗試回答這個問題,盡管事實上答案與我的期望相反,但我仍在發帖。 (雖然我可能在某個地方犯了一個錯誤 - 我幾乎都希望如此,並且對我的測試技術持批評態度。)

  // 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%的沖突。

所以我的“直覺”是錯誤的,似乎沒有理由試圖找到更好的哈希碼技術。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM