c#中通过树迭代的微优化
本文关键字:优化 迭代 | 更新日期: 2023-09-27 18:12:30
我正在做一个大规模的数字处理项目。我从一开始就一直在优化一切,因为我知道这很重要。做性能分析时,我的代码在一个函数上花费了将近40%的生命周期——二叉树迭代器。
public ScTreeNode GetNodeForState(int rootIndex, float[] inputs)
{
0.2% ScTreeNode node = RootNodes[rootIndex].TreeNode;
24.6% while (node.BranchData != null)
{
0.2% BranchNodeData b = node.BranchData;
0.5% node = b.Child2;
12.8% if (inputs[b.SplitInputIndex] <= b.SplitValue)
0.8% node = b.Child1;
}
0.4% return node;
}
有没有c#优化专家有进一步优化的建议?所有比较都是浮点数。我知道,在理论上,这应该无关紧要,但我使用字段,而不是属性,以确保优化。这里的一小部分节省可以节省几天的时间。
请不要回复说"这些优化在现实世界中不重要"-因为在这种情况下他们确实重要。: -)
编辑:我已经按照下面的注释更新了代码,并添加了每行代码的性能分析输出。正如您所看到的,主要的杀手是空检查——为什么?我尝试在节点上使用布尔标志IsLeaf,而不是null检查,但这一行的性能影响是一样的。 分支节点对象的代码如下:public sealed class BranchNodeData
{
/// <summary>
/// The index of the data item in the input array on which we need to split
/// </summary>
internal int SplitInputIndex = 0;
/// <summary>
/// The value that we should split on
/// </summary>
internal float SplitValue = 0;
/// <summary>
/// The nodes children
/// </summary>
internal ScTreeNode Child1;
internal ScTreeNode Child2;
}
另一个编辑:这里还有更多的思考…我想知道为什么
这行BranchNodeData b = node.BranchData;
的执行率为0.2%,null比较线的执行率为17.7%。我猜这是一个分支预测失败?虽然这个比较会被执行多次,并且几乎总是返回true,但这使得CPU很难预测何时返回false。我对CPU的底层工作不是很了解,但情况可能是这样吗?
只是重写了一些代码。这可能有帮助,因为它至少避免了两次跳跃。
public ScTreeNode GetNodeForState(int rootIndex, float[] inputs)
{
ScTreeNode node = RootNodes[rootIndex].TreeNode;
while (node.BranchData != null)
{
BranchNodeData b = node.BranchData;
node = b.Child2;
if (inputs[b.SplitInputIndex] <= b.SplitValue))
node = b.Child1;
}
return node;
}
BranchNodeData看起来像一个引用类型。它只占运行时间的0.2%,因为它只是创建一个指向已经存在的数据的指针,而不是实际复制或分配任何东西。
您可能会在null检查上遇到这样的命中,因为CLR必须进行强制转换以检查您粘贴的密封类。检查是否为空不一定是你想要的。有很多方法可以修改这个类,给你一个布尔值来检查,而不需要太多的计算能力。我更倾向于让ScTreeNode类提供这些
考虑到其他答案中关于缓存的要点,但与空检查无关,请尝试对BranchNodeData
字段的引用进行排序,以便第一个引用允许将所有以下字段加载到缓存中。
也就是说,当Child2
在当前代码中首先被引用时,我假设抖动或CPU不够聪明,无法"向后"加载到缓存SplitInputIndex
, SplitValue
和Child1
。
所以要么改变BranchNodeData
类中字段的顺序,要么把set; if ... overwrite;
改成if ... else
。