[英]Best way to GetHashCode() for 44-bit number stored as Int64
我有大約5,000,000個對象存儲在Dictionary<MyKey, MyValue>
。
MyKey
是一個結構,它將我的密鑰的每個組件(5個不同的數字)打包在Int64
( ulong
)的最右邊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.