C# 为什么HashSet<;点>;比HashSet慢得多<;字符串>;?
我想在不允许重复的情况下存储一些像素位置,所以首先想到的是C# 为什么HashSet<;点>;比HashSet慢得多<;字符串>;?,c#,.net,performance,collections,hashset,C#,.net,Performance,Collections,Hashset,我想在不允许重复的情况下存储一些像素位置,所以首先想到的是HashSet或类似的类。然而,与HashSet之类的东西相比,这似乎非常慢 例如,此代码: HashSet<Point> points = new HashSet<Point>(); using (Bitmap img = new Bitmap(1000, 1000)) { for (int x = 0; x < img.Width; x++) { for (int y =
HashSet
或类似的类。然而,与HashSet之类的东西相比,这似乎非常慢
例如,此代码:
HashSet<Point> points = new HashSet<Point>();
using (Bitmap img = new Bitmap(1000, 1000))
{
for (int x = 0; x < img.Width; x++)
{
for (int y = 0; y < img.Height; y++)
{
points.Add(new Point(x, y));
}
}
}
HashSet points=newhashset();
使用(位图img=新位图(10001000))
{
对于(int x=0;x
大约需要22.5秒
而以下代码(由于明显的原因,这不是一个好的选择)只需1.6秒:
HashSet<string> points = new HashSet<string>();
using (Bitmap img = new Bitmap(1000, 1000))
{
for (int x = 0; x < img.Width; x++)
{
for (int y = 0; y < img.Height; y++)
{
points.Add(x + "," + y);
}
}
}
HashSet points=newhashset();
使用(位图img=新位图(10001000))
{
对于(int x=0;x
所以,我的问题是:
- 这有什么原因吗?我查过了,但22.5秒比答案中显示的数字要多得多
- 有没有更好的方法来存储不重复的点
Equals(objectobj)
的调用,从而增加装箱转换的数量
还请注意,这是通过x^y
计算的。这在您的数据范围内产生的离散度很小,因此散列集
的存储桶人口过多,而字符串
中散列的离散度要大得多
您可以通过实现自己的Point
struct(琐碎)并针对预期的数据范围使用更好的哈希算法来解决该问题,例如,通过移动坐标:
(x << 16) ^ y
(x点结构导致了两个性能问题。添加Console.WriteLine(GC.CollectionCount(0))时可以看到这两个问题;
到测试代码。你会看到点测试需要~3720个集合,而字符串测试只需要~18个集合。这不是免费的。当你看到一个值类型诱导了这么多集合时,你需要总结“哦,太多的装箱”
问题在于HashSet
需要一个IEqualityComparer
来完成它的工作。因为您没有提供一个,所以它需要返回到EqualityComparer.Default()返回的值
。该方法可以很好地处理字符串,它实现了IEquatable。但就这点而言,它是一种从.NET 1.0开始的类型,从未受到泛型的喜爱。它所能做的就是使用对象方法
另一个问题是,Point.GetHashCode()在这个测试中做得并不出色,因为冲突太多,所以它对Object.Equals()的影响很大。String有一个出色的GetHashCode实现
您可以通过为哈希集提供一个好的比较器来解决这两个问题。如下所示:
class PointComparer : IEqualityComparer<Point> {
public bool Equals(Point x, Point y) {
return x.X == y.X && x.Y == y.Y;
}
public int GetHashCode(Point obj) {
// Perfect hash for practical bitmaps, their width/height is never >= 65536
return (obj.Y << 16) ^ obj.X;
}
}
类指针比较器:IEqualityComparer{
公共布尔等于(点x,点y){
返回x.x==y.x&&x.y==y.y;
}
公共int GetHashCode(点obj){
//实用位图的完美散列,其宽度/高度从不>=65536
return(obj.Y@MartinSmith,因为没有其他好的理由,如果一个哈希集在相同的情况下对一种类型慢,而对另一种类型慢,那么它必须是由于慢类型的哈希代码实现没有产生足够的分散度。查看GetHashCode
执行点的参考源:未选中(x^y)
而对于string
它看起来要复杂得多。@AhmedAbdelhameed这可能是因为您向哈希集中添加的成员比您想象的要少(同样是由于哈希代码算法的可怕分散)。当您完成填充后,列表的计数是多少?@Ahmedabdelhamed您的测试是错误的。您反复添加相同的长度,因此实际上插入的元素很少。当插入点时,哈希集
将在内部调用GetHashCode
,并针对每个点使用相同hashcode的ts将调用Equals
来确定它是否已经存在。当您可以创建一个实现IEqualityComparer
的类,并与使用Point
的其他东西保持兼容时,就不需要实现Point
,同时获得不使用穷人GetH的好处ashCode
和需要在中加框等于()
+1,用于提供GetHashCode方法实现。出于好奇,您是如何获得特定的obj.X的?它的灵感来自于鼠标在windows中通过其位置的方式。它是您想要显示的任何位图的完美哈希。很高兴知道这一点。有任何文档或最佳指南来编写类似您的hashcode吗?Actually,我仍然想知道上面的哈希代码是你的经验还是你遵循的任何准则。@AkashKC我对C#不是很有经验,但据我所知,整数通常是32位。在这种情况下,你需要2个数字的哈希,通过左移1个16位,你可以确保每个数字的“低”16位不会“影响”另一个是带有|
。对于3个数字,使用22和11作为移位是有意义的。对于4个数字,它将是24、16、8。但是,仍然会有冲突,但只有当数字变大时才会发生。但这也关键取决于哈希集
的实现。如果它使用带“位截断”的开放寻址(我认为不会!)左移位方法可能不好。@HansPassant:我想知道在GetHashCode中使用XOR而不是OR是否会稍微好一点——如果点坐标可能超过16位(可能是n位)
HashSet<Point> list = new HashSet<Point>(new PointComparer());