C# 为什么我的字典在使用C中的复合键时表现不佳?

C# 为什么我的字典在使用C中的复合键时表现不佳?,c#,performance,dictionary,recursion,C#,Performance,Dictionary,Recursion,我有一个使用递归遍历树并更新项的方法 目前,该方法需要相当长的时间来处理所有项目,所以我开始优化事情。其中包括使用字典,而不是对每个项执行DB查询 字典的定义是 System.Collections.Generic.Dictionary<EffectivePermissionKey, MyData> private struct EffectivePermissionKey { // http://blog.martindoms.com/2011/01/03/c-tip-ove

我有一个使用递归遍历树并更新项的方法

目前,该方法需要相当长的时间来处理所有项目,所以我开始优化事情。其中包括使用字典,而不是对每个项执行DB查询

字典的定义是

System.Collections.Generic.Dictionary<EffectivePermissionKey, MyData>
private struct EffectivePermissionKey
{
  // http://blog.martindoms.com/2011/01/03/c-tip-override-equals-on-value-types-for-better-performance/
  public override bool Equals(object aObject)
  {
    if (aObject == null)
      return false;
    else
      return aObject is EffectivePermissionKey && Equals((EffectivePermissionKey)aObject);
  }

  public bool Equals(EffectivePermissionKey aObject)
  {
    return this.ID == aObject.ID && this.OrchardUserID == aObject.OrchardUserID;
  }

  public override int GetHashCode()
  { 
    // http://stackoverflow.com/a/32502294/3936440
    return unchecked(ID.GetHashCode() * 23 * 23 + OrchardUserID.GetHashCode() * 23);
  }

  public int ID;
  public int OrchardUserID;
}
当该方法运行时,大约需要5000次递归来更新所有项

最初,没有字典的情况下大约需要100秒

第一种方法是使用带int键的字典替换DB查询,耗时22秒


现在,DB查询被上面定义的字典和正确的TryGetValue调用所取代,这需要97秒各位,抱歉耽误了你们的时间,我的方法是完全错误的。让我告诉你原因

为了简单起见,问题被细分为:

A -> recursion 1, DB query for permission of node A with ID = 1
  B -> recursion 2, DB query for permission of node B with ID = 2
  C -> recursion 3, DB query for permission of node C with ID = 3
    D -> recursion 4, DB query for permission of node D with ID = 4
如您所见,每个树节点一个DB查询

现在,有缺陷的优化方法是:

Dictionary<int, PermissionData> myMap

...

DB query of all permissions and insert into myMap

...

A -> recursion 1, myMap.TryGetValue(1, out ...)
  B -> recursion 2, myMap.TryGetValue(2, out ...)
  C -> recursion 3, myMap.TryGetValue(3, out ...)
    D -> recursion 4, myMap.TryGetValue(4, out ...)
现在您可以看到,查询只执行了一次,但在每个节点上都进行了aTryGetValue调用

在我的特定情况下,这实际上比执行单个查询慢,因为

字典包含的键与节点存在的键一样多,因为每个节点都有一个DB权限条目 及

每个TryGetValue需要/导致

创建具有ID和用户ID的密钥实例 调用TryGetValue 计算密钥实例的哈希值 呼唤平等 这4个步骤执行大约5000次,而执行5000个简单实体框架查询SELECT*FROM表,其中ID=。。。。我不知道为什么,但是这里的查询速度更快,也许编译器优化了一些东西

无论如何,我重新设计了整个过程,现在我有了一个用户ID上的外部循环和一个内部递归遍历,它使用带有简单int键节点ID的字典。它给了我快速的结果。整个执行过程现在大约需要16秒,再加上一些调整和线程,我把时间缩短到了1秒以下。任务完成了

编辑

在与同事讨论这个问题后,我们得出结论,性能问题很可能是由哈希代码计算中使用的素数引起的。我使用了23x23x23,但为了避免冲突,它应该类似于17x23x23,但我无法测试它,因为相关的代码/应用程序不再由我负责。顺便说一句,一般的解决方案可以在这里找到:

编辑2


正如一位同事指出的,下面的答案建议不要使用17和23,而是使用更大的素数,请参见

我看不出Equals和GetHashCode有任何问题,我确实更喜欢这里提供的17/23累积值,但您的非累积值版本不应该引起太多冲突,可能是因为装箱/取消装箱?EffectivePermissionKey未实现IEquatable这意味着字典将使用ObjectEqualityCompare您没有在结构中实现IEquatable,因此结构将被装箱,这会影响性能。关于GetHashCode和Equals:具有不同哈希的两个对象永远不应相等,但是具有相同哈希的两个对象可能仍然不同。这就是为什么一个相等的散列后面跟着一个相等检查,然后才返回该项。你能发布一个显示该问题的完整文件吗?我严重怀疑单个字典查找比数据库查询慢,这里有一些其他的东西,所以我不认为这是一个很好的回答你的问题老实说。这可以解释为什么你最终会得到一个性能很差的字典,在某些情况下,它可能会对你的对象进行线性搜索。
Dictionary<int, PermissionData> myMap

...

DB query of all permissions and insert into myMap

...

A -> recursion 1, myMap.TryGetValue(1, out ...)
  B -> recursion 2, myMap.TryGetValue(2, out ...)
  C -> recursion 3, myMap.TryGetValue(3, out ...)
    D -> recursion 4, myMap.TryGetValue(4, out ...)