【发布时间】:2013-05-07 09:51:17
【问题描述】:
我正在从事一个大规模数字运算项目。从一开始我就一直在优化一切,因为我知道这很重要。进行性能分析时,我的代码将其生命周期的近 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# 优化专家对进一步优化有任何提示?所有比较都是浮点数。我知道理论上这无关紧要,但我使用的是字段而不是属性,因此请确保优化。这里的小额节省可能会缩短处理时间。
请不要回复说“这些优化在现实世界中无关紧要” - 因为在这种情况下它们确实如此。 :-)
编辑:我已经按照下面的 cmets 将代码更新为现在的代码,并在每行代码的性能分析输出中添加。如您所见,主要杀手是空检查 - 为什么?我尝试在节点上使用布尔标志 IsLeaf 而不是空检查,但它对该行的性能相同。
分支节点对象代码如下:
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% 的执行,而空比较行记录了 17.7%。我猜这是分支预测失败?虽然该比较被多次命中,并且几乎总是返回 true,但 CPU 很难预测它何时会返回 false。我不太了解 CPU 的低级工作原理,但这可能是这种情况吗?
【问题讨论】:
-
一个想法是预取状态(到字典中)并在数据更改时更新它们。这样,当您需要获取节点的状态时,操作将在 O( 1)
-
只是从 C++ 的角度思考......我不知道 C# 有多少可能,但是当你穿过一棵树时,你已经失去了缓存局部性。如果你所有的对象都分散在内存中,那么局部性就消失了。如果您的树很密集,请考虑将其存储为数组或使用内存池(如果您可以在 C# 中执行此操作)。您也将成为此处分支预测的受害者。不确定您是否可以为此做很多事情。
-
一个次要的事情可能是将
node.BranchData存储在一个临时变量中,而不是在每次while循环迭代中加载该字段三次。 -
尝试用
node = node.BranchData.Children[(1 + Math.Sign(inputs[node.BranchData.SplitInputIndex] - node.BranchData.SplitValue))/2];替换if -
@WillCalderwood 因为这是一棵二叉树(我认为),是否有可能放弃
Children集合而直接引用Child1和Child2并避免集合查找?
标签: c# optimization binary-tree