【问题标题】:Request for Comments: fast hashed base class for dictonary keys征求意见:字典键的快速散列基类
【发布时间】:2010-08-08 07:55:05
【问题描述】:

在我的一个应用程序中,我必须使用许多带有自定义对象作为键的字典。为了提高查找的性能,我实现了一个覆盖 GetHashCode 的基类。 它似乎可以工作,但不知何故我仍然对它有一种不好的感觉,所以我决定发布我的代码,如果有任何提示或意见,我将不胜感激。 (天哪,我忘记了代码:D)

abstract class FastHashed
{
    private static Dictionary<Type,ulong> _instanceCounters = new Dictionary<Type,ulong>();

    private int hash;

    protected FastHashed()
    {
        Type instanceType = this.GetType();
        if(! _instanceCounters.ContainsKey(instanceType)) _instanceCounters.Add(instanceType,0);  
        this.hash = ((instanceType.ToString())+(_instanceCounters[instanceType]++.ToString())).GetHashCode();
    }

    public override int  GetHashCode()
    {
         return hash;
    }
}

编辑:如果你不需要,不要乱用散列。这个“解决方案”比默认的 GetHashCode() 更慢,更不可靠。

编辑: 我使用 Equatec 分析器和一个简单的控制台应用程序进行了一些性能测试。

类程序 { 静态只读 int 周期 = 50000; 静态字典 objectsDict = new Dictionary(); static Dictionary foosDict = new Dictionary();

    static void Main(string[] args)
    {

        foo[] foos = new foo[cycles];
        object[] objects = new object[cycles];


        for (int i = 0; i < cycles; i++)
        {
            foos[i] = new foo();
            objects[i] = new object();
            foosDict.Add(foos[i], i);
            objectsDict.Add(objects[i], i);
        }

        ObjectHash(objects);
        FooHash(foos);

    }

    static void ObjectHash(Object[] objects)
    {
        int value; 
        for (int i = 0; i < cycles; i++)
        {
            value = objectsDict[objects[i]];
        }

    }
    static void FooHash(foo[] foos)
    {
        int value;
        for (int i = 0; i < cycles; i++)
        {
            value = foosDict[foos[i]];
        }
    }

    class foo
    {
        private readonly int _hash;
        public foo()
        {
            _hash = this.GetHashCode();
        }
        public override int GetHashCode()
        {
            return _hash;
        }
    }
}

结果: - FooHash 26 774 毫秒 - 对象哈希 7 毫秒

显然默认的 GetHashCode 是最好的选择。

【问题讨论】:

  • 我个人不明白这段代码的意义。

标签: c# performance hash


【解决方案1】:
  1. 这不是线程安全的。
  2. 如果您只关心引用相等性,为什么要为不同类型设置不同的计数器?

如果你想要的只是防止哈希被计算多次,为什么不这样做(或者如果字典只包含某种类型的对象,则使用泛型的变体):

  public class ObjectWithCachedHashCode : IEquatable<ObjectWithCachedHashCode>
    {
        private int _cachedHashCode;

        public object Object { get; private set; }

        public ObjectWithCachedHashCode(object obj)
        {
            Object = obj;
            _cachedHashCode = obj.GetHashCode();
        }

        public override int GetHashCode()
        {
            return _cachedHashCode;
        }

        public bool Equals(ObjectWithCachedHashCode other)
        {
            return other!=null && Object.Equals(other.Object);
        }

        public override bool Equals(object other)
        {
            return Equals(other as ObjectWithCachedHashCode);
        }


    }

编辑:使类与字典兼容

【讨论】:

  • 好吧,线程安全扼杀了这个概念。如果我必须同步构造函数调用,这会影响我的性能,因为我必须在多个线程中生成大量对象。你确定 obj.GetHashCode() 会返回一个唯一值吗?使用类型名和单独的计数器背后的想法是确保哈希码在不同的派生类中是唯一的。
  • 1. GetHashCode() 不必是唯一的,大多数键值结构将其视为相等的必要但不充分条件。例如,Dictionary 将值组织到哈希桶中,但在检索期间检查每个桶内的完全相等。 2. 如果可能,尝试在派生类中明智地覆盖 GetHashCode()。如果这不是一个选项,请坚持使用 Object.GetHashCode(),因为无论如何您只关心引用相等。这势必会以减少冲突的方式实现,尽管它没有指定,并且实现方式可能会在未来发生变化。
  • @Ani:您的缓存哈希码只会被那些使用 HashCode 属性(没有人)的人使用。调用 GetHashCode 的每个人(即每个人)仍然会得到每次计算的哈希码。
  • 是的,没错。我已将其更新为标准化。另请注意,我故意不处理 Object 为空的情况;在这种情况下,应将类修改为用户认为正确的行为。
  • 非常感谢。在我的情况下,我将坚持缓存 Object.GetHashCode,因为我无法从对象属性中获取唯一值。附加亲:这个实现要简单得多,我可以很容易地在从其他类派生的类上使用它并使用父哈希。
【解决方案2】:

您可以将哈希变量标记为readonly

但老实说,在具有单一继承的 C# 中,“浪费”继承来实现这种特定行为并不总是明智的。假设您突然想要从“做”某事的基类继承。将类继承保存到建模目的,而不是实现细节。

【讨论】:

  • 在大多数情况下,您绝对正确。但是我将这个基类用于一些从对象和多个接口继承的类,所以浪费我的继承不是问题。
  • 我阅读了一篇关于如何加快查找速度的文章和讨论。然而,海斯写道,他不知道他在做什么;)
【解决方案3】:

据我所知,这在功能上等同于 object.GetHashCode() 默认实现,除了速度较慢且非线程安全之外。是什么让“Fast Hash”快速?

【讨论】:

  • 我同意这不是线程安全的。但是为什么会慢呢? Object.GetHashCode 是否兑现其结果?我发现很多线程建议创建一个私有哈希属性并返回它,而不是在每次调用时创建一个新的哈希。但是正如我在问题中提到的那样,我对此确实有不好的感觉。
  • object.GetHashCode() 根据初始位置快速得到结果。这有一个基于查找在构造中使用相同 object.GetHashCode() 的对象哈希的实现,以播种不同的快速获得的结果。您所做的只是增加了创建时间(特别是当您将其修复为处理多线程的真实代码时,这本身会创建对象 - 通常是线程安全的 - 与所有其他派生对象的潜在争用点从这种类型)。该锁定可能会使其速度变慢(在更坏的情况下创建时间无限)
  • 就此而言,因为你是在身份而不是值上进行散列(即使所有属性都相同,每个对象都有不同的散列码),并且因为它们是你自己的对象,你能不能将“值”作为属性添加到“键”对象中,而不是对它们进行散列?无论如何,每个对象都将作为唯一值的键,所以与其根据散列查找,为什么不直接访问该属性?
  • 感谢您的详细解答。我写这篇文章是因为我读到了一些关于 GerHashCode 及其性能的非常不同的东西。你说的更有意义。
  • 由于您的哈希生成 - 被视为哈希 - 不比 object 更好,并且您唯一关心的是返回哈希的速度,您可以缓存默认结果:protected FastHashed(){hash = base.GetHashCode();} I '我不确定我是否会打扰,虽然我已经记住了 GetHashCode() 的值类型,但值得尝试(为同一个对象调用 GetHashCode 数千次)。
猜你喜欢
  • 2011-10-01
  • 1970-01-01
  • 2019-10-16
  • 2017-11-11
  • 1970-01-01
  • 1970-01-01
  • 2019-01-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多