我有一个使用递归来遍历树并更新项目的方法。
目前该方法需要很长时间来处理所有项目,所以我开始优化。其中包括使用字典而不是为每个项目执行数据库查询。
字典定义为
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
密钥的字典替换数据库查询的第一种方法花了 22秒。
现在,将DB查询替换为使用上面定义的字典并进行适当的TryGetValue()
调用,需要 97秒&lt; - WAT。
这里发生了什么?什么可能导致这种巨大的性能下降?
修改
起初,它看起来像是一个哈希冲突问题,所以我在EffectivePermissionKey.Equals()
中添加了一个断点来验证这个方法是否被调用但是没有被调用,因此我认为没有哈希冲突。
EDIT2
现在我很困惑。我认为只有在哈希码不匹配时才会调用Equals()
。打印出我的密钥的哈希码和TryGetValue()
中使用的密钥后,我看到这些代码匹配。然后我查看了Dictionary<>
的源代码,FindEntry()
中有一行看起来像这样:
if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) return i;
这意味着对于字典中的每个项目键,GetHashCode()
和 Equals()
会被调用,因为我处理字典中的所有项目,因为项目是数据库查询,而这些结果无论如何都要在字典方法之前处理。
答案 0 :(得分:3)
没关系,不好意思花时间,我的方法完全错了。我告诉你为什么。
为简单起见,问题已经解决:
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
如您所见,每个树节点有一个数据库查询。
现在有缺陷的优化方法:
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 ...)
您现在看到查询已完成一次,但在每个节点上都进行了TryGetValue()
调用。
在我的特定情况下,这实际上比执行单个查询要慢,因为
和
每个TryGetValue()
需要/结果
TryGetValue()
Equals()
与执行5000个简单实体框架查询(SELECT * FROM table WHERE ID = ...
)相比,这4个步骤执行约5000次。我不知道为什么,但这里的查询速度更快,也许编译器可以优化一些东西。
无论如何,我重写了整个事情,现在我有一个外部循环用户ID和一个内部递归遍历女巫使用带有简单的int键(节点ID)的字典。它给我照明快速的结果。整个执行现在需要大约16秒,并且通过一些调整和线程我将其降低到不到1秒。完成任务。
修改强>
在与同事讨论此问题后,我们得出结论,性能问题很可能是由哈希码计算中使用的素数引起的。我使用23 x 23 x 23,但它应该像17 x 23 x 23,以避免碰撞,但我不能测试这个,因为相关的代码/应用程序不再是我的责任。顺便说一下,通用解决方案可以在这里找到:https://stackoverflow.com/a/763966/3936440
修改2
如同一位同事所述,以下答案建议不使用17和23,而是使用更大的素数,请参阅https://stackoverflow.com/a/38281271/3936440