我在Dictionary<MyKey, MyValue>
中存储了大约5,000,000个对象。
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()
?
答案 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%的冲突。
所以我的“直觉”是错误的,似乎没有理由试图找到更好的哈希码技术。