【问题标题】:Slow dictionary with custom class key带有自定义类键的慢速字典
【发布时间】:2015-06-03 14:49:36
【问题描述】:

我有一个自定义类,我试图将其用作字典的键:

// I tried setting more than enough capacity also...
var dict = new Dictionary<MyPoint, MyPoint>(capacity);

现在让我明确一点,这里的目标是比较两个相似但不同的列表,使用 XYDate 作为复合键。这两个列表的值会有所不同,我正在尝试快速比较它们并计算它们的差异。

这是课程代码:

public class MyPoint : IEquatable<MyPoint>
{
    public short X { get; set; }
    public short Y { get; set; }
    public DateTime Date { get; set; }
    public double MyValue { get; set; }

    public override bool Equals(object obj)
    {
        return base.Equals(obj as MyPoint);
    }

    public bool Equals(MyPoint other)
    {
        if (other == null)
        {
            return false;
        }

        return (Date == other.Date)
            && (X == other.X)
            && (Y == other.Y);
    }

    public override int GetHashCode()
    {
        return Date.GetHashCode()
             | X.GetHashCode()
             | Y.GetHashCode();
    }
}

我还尝试使用结构进行键控:

public struct MyPointKey
{
    public short X;
    public short Y;
    public DateTime Date;
    // The value is not on these, because the struct is only used as key
}

在这两种情况下,字典的书写速度都非常非常慢(阅读速度很快)。

我把key改成了字符串,格式为:

var dict = new Dictionary<string, MyPoint>(capacity);
var key = string.Format("{0}_{1}", item.X, item.Y);

我对它的速度之快感到惊讶——它至少快了 10 倍。我尝试了发布模式,没有调试器,以及我能想到的所有场景。

这本词典将包含 350,000 或更多项,因此性能很重要。

有什么想法或建议吗?谢谢!

另一个编辑...

我正在尝试以最快的方式比较两个列表。这就是我正在使用的。字典对于快速查找源列表很重要。

IList<MyThing> sourceList;
IDictionary<MyThing, MyThing> comparisonDict;

Parallel.ForEach(sourceList,
    sourceItem =>
    {
        double compareValue = 0;

        MyThing compareMatch = null;
        if (comparisonDict.TryGetValue(sourceItem, out compareMatch))
        {
            compareValue = compareMatch.MyValue;
        }

        // Do a delta check on the item
        double difference = sourceItem.MyValue- compareValue;
        if (Math.Abs(difference) > 1)
        {
            // Record the difference...
        }
    });

【问题讨论】:

  • 您已经回答了自己的问题 - 使用复杂类型作为字典的键比使用原始类型慢。
  • 记住——不要使用可变对象作为字典键。同样使用不可变点,您可以在点初始化期间计算和存储哈希码。因此,您将拥有原始类型的速度
  • 另外,GetHashCode() 使用 |这可能不是最好的方法。
  • @BinkanSalaryman 通常你使用^ (XOR) 来组合哈希码。
  • @BinkanSalaryman 我知道它是什么。我只是说这不是你实现哈希码的方式。一种流行的方法是将每个单独的哈希码乘以一个素数并将它们相加。

标签: c# string dictionary equals gethashcode


【解决方案1】:

正如其他人在 cmets 中所说,问题出在您的 GetHashCode() 实施中。获取您的代码并使用字符串键运行 10,000,000 次迭代需要 11-12 秒。使用您现有的 hashCode 运行我在一分钟后停止了它。使用以下 hashCode 实现花费不到 5 秒。

public override int GetHashCode()
{
    var hashCode = Date.GetHashCode();
    hashCode = (hashCode * 37) ^ X.GetHashCode();
    hashCode = (hashCode * 37) ^ Y.GetHashCode();
    return hashCode;
}

问题在于,当您获得大量数据时,由于ORs,所有项目都在同一个桶中发生碰撞。所有内容都在同一个存储桶中的字典只是一个列表。

【讨论】:

  • 哇,带来了巨大的不同。谢谢!我所做的唯一编辑是将代码包装在 unchecked 块中,以便允许整数翻转。现在可以立即创建字典!
【解决方案2】:

如果我没听错的话,你喜欢使用一组同时仍保持键的顺序。在这种情况下,请改用SortedSet`1

代码:

class Program {
    static void Main(string[] args) {
        SortedSet<MyKey> list = new SortedSet<MyKey>() {
             new MyKey(0, 0, new DateTime(2015, 6, 4)),
            new MyKey(0, 1, new DateTime(2015, 6, 3)),
            new MyKey(1, 1, new DateTime(2015, 6, 3)),
            new MyKey(0, 0, new DateTime(2015, 6, 3)),
            new MyKey(1, 0, new DateTime(2015, 6, 3)),

        };
        foreach(var entry in list) {
            Console.WriteLine(string.Join(", ", entry.X, entry.Y, entry.Date));

        }
        Console.ReadKey();
    }
}

我将您的MyPoint 类更改如下:

public sealed class MyKey : IEquatable<MyKey>, IComparable<MyKey> {
    public readonly short X;
    public readonly short Y;
    public readonly DateTime Date;

    public MyKey(short x, short y, DateTime date) {
        this.X = x;
        this.Y = y;
        this.Date = date;
    }

    public override bool Equals(object that) {
        return this.Equals(that as MyKey);
    }

    public bool Equals(MyKey that) {
        if(that == null) {
            return false;
        }

        return this.Date == that.Date
            && this.X == that.X
            && this.Y == that.Y;
    }

    public static bool operator ==(MyKey lhs, MyKey rhs) {
        return lhs != null ? lhs.Equals(rhs) : rhs == null;
    }

    public static bool operator !=(MyKey lhs, MyKey rhs) {
        return lhs != null ? !lhs.Equals(rhs) : rhs != null;
    }

    public override int GetHashCode() {
        int result;
        unchecked {
            result = (int)X;
            result = 31 * result + (int)Y;
            result = 31 * result + Date.GetHashCode();
        }
        return result;
    }

    public int CompareTo(MyKey that) {
        int result = this.X.CompareTo(that.X);
        if(result != 0) {
            return result;
        }
        result = this.Y.CompareTo(that.Y);
        if(result != 0) {
            return result;
        }
        result = this.Date.CompareTo(that.Date);
        return result;
    }
}

输出:

0, 0, 03.06.2015 00:00:00
0, 0, 04.06.2015 00:00:00
0, 1, 03.06.2015 00:00:00
1, 0, 03.06.2015 00:00:00
1, 1, 03.06.2015 00:00:00

【讨论】:

  • 感谢您的帖子,但我不确定使用 SortedSet 如何有助于缩短查找时间。你能举个例子吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-22
  • 1970-01-01
  • 2016-11-13
  • 2011-06-21
  • 2021-11-22
  • 1970-01-01
相关资源
最近更新 更多