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的底层工作不是很了解,但情况可能是这样吗?

c#中通过树迭代的微优化

只是重写了一些代码。这可能有帮助,因为它至少避免了两次跳跃。

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, SplitValueChild1

所以要么改变BranchNodeData类中字段的顺序,要么把set; if ... overwrite;改成if ... else