[英]Can a custom GetHashcode implementation cause problems with Dictionary or Hashtable's “buckets”
[英]GetHashCode and Buckets
我试图更好地了解散列集(例如HashSet<T>
如何工作以及它们为何表现出色。 我发现了以下文章,使用存储桶列表http://ericlippert.com/2011/02/28/guidelines-and-rules-for-gethashcode/实现了一个简单示例。
据我对本文的理解(我之前也曾这么认为),存储桶列表本身将每个存储桶中的一定数量的元素分组。 一个存储桶由哈希码表示,即由在元素上调用的GetHashCode
表示。 我认为更好的性能是基于以下事实:存储桶少于元素。
现在,我编写了以下朴素的测试代码:
public class CustomHashCode
{
public int Id { get; set; }
public override int GetHashCode()
{
//return Id.GetHashCode(); // Way better performance
return Id % 40; // Bad performance! But why?
}
public override bool Equals(object obj)
{
return ((CustomHashCode) obj).Id == Id;
}
}
这是探查器:
public static void TestNoCustomHashCode(int iterations)
{
var hashSet = new HashSet<NoCustomHashCode>();
for (int j = 0; j < iterations; j++)
{
hashSet.Add(new NoCustomHashCode() { Id = j });
}
var chc = hashSet.First();
var stopwatch = new Stopwatch();
stopwatch.Start();
for (int j = 0; j < iterations; j++)
{
hashSet.Contains(chc);
}
stopwatch.Stop();
Console.WriteLine(string.Format("Elapsed time (ms): {0}", stopwatch.ElapsedMilliseconds));
}
我的天真想法是:让我们减少存储桶的数量(使用简单的模),这可以提高性能。 但这是可怕的(在我的系统上,迭代5万次大约需要4秒钟)。 我还认为,如果我只是将Id作为哈希码返回,则性能会很差,因为最终会得到50000个存储桶。 但是情况恰恰相反,我想我只是产生了所谓的碰撞声,而不是改善任何东西。 但是话又说回来,存储桶列表如何工作?
A Contains
检查基本上:
通过限制存储桶的数量,您增加了每个存储桶中的项目数量,从而增加了哈希集必须迭代通过的项目数量,以检查是否相等,以查看某个项目是否存在。 因此,需要更长的时间才能查看给定的项目是否存在。
您可能已经减少了哈希集的内存占用; 你甚至可能减少插入时间,但我对此表示怀疑。 您尚未减少存在检查时间。
减少存储桶数量不会提高性能。 实际上, Int32
的GetHashCode
方法本身返回整数值,这对于性能而言是理想的,因为它将产生尽可能多的存储桶。
赋予哈希表性能的是密钥到哈希码的转换,这意味着它可以快速消除集合中的大多数项目。 它必须考虑的唯一项目是同一存储桶中的项目。 如果您的水桶很少,则意味着它可以淘汰少得多的物品。
最糟糕的GetHashCode
实现将导致所有项目进入同一存储桶:
public override int GetHashCode() {
return 0;
}
这仍然是有效的实现,但是这意味着哈希表具有与常规列表相同的性能,即,它必须遍历集合中的所有项目以找到匹配项。
一个简单的HashSet<T>
可以这样实现(只是一个草图,不会编译)
class HashSet<T>
{
struct Element
{
int Hash;
int Next;
T item;
}
int[] buckets=new int[Capacity];
Element[] data=new Element[Capacity];
bool Contains(T item)
{
int hash=item.GetHashCode();
// Bucket lookup is a simple array lookup => cheap
int index=buckets[(uint)hash%Capacity];
// Search for the actual item is linear in the number of items in the bucket
while(index>=0)
{
if((data[index].Hash==hash) && Equals(data[index].Item, item))
return true;
index=data[index].Next;
}
return false;
}
}
如果您查看此内容,则在Contains
中搜索的成本与存储桶中的项目数成正比。 因此,拥有更多的存储桶会使搜索更加便宜,但是一旦存储桶数量超过了商品数量,其他存储桶的收益就会迅速减少。
具有不同的哈希码还可以作为比较存储桶中对象的早期方法,从而避免了潜在的昂贵Equals
调用。
简而言之, GetHashCode
应该尽可能多样化。 HashSet<T>
的工作是将大空间减少到适当数量的存储桶,这大约是集合中项目的数量(通常在两倍之内)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.