HashSet <t> GetHashcode优化</t>

时间:2013-07-14 23:29:04

标签: c# performance graph hashcode

我在C#中有以下结构来表示图形边缘:

struct Edge
{
    public Edge(int leftA, int leftB, int leftC, int leftD, int rightA, int rightB, int rightC, int rightD)
    {
        LeftIdA = leftA;
        LeftIdB = leftB;
        LeftIdC = leftC;
        LeftIdD = leftD;

        RightIdA = rightA;
        RightIdB = rightB;
        RightIdC = rightC;
        RightIdD = rightD;
    }

    public readonly int LeftIdA;
    public readonly int LeftIdB;
    public readonly int LeftIdC;
    public readonly int LeftIdD;

    public readonly int RightIdA;
    public readonly int RightIdB;
    public readonly int RightIdC;
    public readonly int RightIdD;
}

并且需要在HashSet中存储大量(大约5百万),因此没有重复项。什么是GetHashCode的良好实现,因此它针对速度进行了优化?

我试图将每个id的4位存储在返回的整数中,如下所示:

    public override int GetHashCode()
    {
        int A = LeftIdA & 0xF;
        int B = LeftIdB & 0xF;
        int C = LeftIdC & 0xF;
        int D = LeftIdD & 0xF;

        int E = RightIdA & 0xF;
        int F = RightIdB & 0xF;
        int G = RightIdC & 0xF;
        int H = RightIdD & 0xF;

        int result = A;
        result = (result << 4) | B;
        result = (result << 4) | C;
        result = (result << 4) | D;
        result = (result << 4) | E;
        result = (result << 4) | F;
        result = (result << 4) | G;
        result = (result << 4) | H;

        return result;
    }

但它比将项目添加到列表中要慢80%。

3 个答案:

答案 0 :(得分:1)

  

什么是GetHashCode的良好实现,因此它针对速度进行了优化?

由于您的所有字段都是只读的,因此最好的办法是在构造函数中预先计算哈希码,然后从GetHashCode返回。

要预先计算哈希码,您可以使用Guffa答案中的公式。

答案 1 :(得分:0)

添加到HashSet需要更长时间,而且不是因为任何糟糕的策略 GetHashCode()实施。实际上,这种实现看起来相当不错。一个HashSet必须做各种疯狂的废话,比如设置水桶并将东西插入其中。

性能增益在于发现hashset中的元素。尝试将500万个不同的项添加到列表和哈希集,并查看哪个容器能够更快地告诉您它是否包含特定Edge。您可能愿意支付不到两倍的设置时间。

答案 2 :(得分:0)

为了达到最佳效果,哈希码应该尽可能少地进行冲突,即产生尽可能多的哈希码。

尝试生成哈希码,以便使用所有成员的所有数据:

public override int GetHashCode() {
  return
    LeftIdA ^ LeftIdB ^ LeftIdC ^ LeftIdD ^
    RightIdA ^ RightIdB ^ RightIdC ^ RightIdD;
}

与素数相乘可以得到非常好的分布,因此您应该测试是否能在您的情况下提供更好的性能:

public override int GetHashCode() {
  return
    ((((((LeftIdA * 251 + LeftIdB) * 251 + LeftIdC) * 251 +
    LeftIdD) * 251 + RightIdA) * 251 + RightIdB) * 251 +
    RightIdC) * 251 + RightIdD;
}

注意:确保您还为结构提供了优化的相等性比较。默认实现将使用反射来确定要比较的所有成员,因此非常慢。

编辑:

我做了一些测试,在第二个实现中,我可以在大约两秒钟内向HashSet添加500万个项目。