javafor循环中的分支预测



我在if条件旁边看到了这个注释:

// branch prediction favors most often used condition

在JavaFX SkinBase类的源代码中。

protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
    double minX = 0;
    double maxX = 0;
    boolean firstManagedChild = true;
    for (int i = 0; i < children.size(); i++) {
        Node node = children.get(i);
        if (node.isManaged()) {
            final double x = node.getLayoutBounds().getMinX() + node.getLayoutX();
            if (!firstManagedChild) {  // branch prediction favors most often used condition
                minX = Math.min(minX, x);
                maxX = Math.max(maxX, x + node.minWidth(-1));
            } else {
                minX = x;
                maxX = x + node.minWidth(-1);
                firstManagedChild = false;
            }
        }
    }
    double minWidth = maxX - minX;
    return leftInset + minWidth + rightInset;
}

我相信开发人员想解释为什么他写了一个否定if.

这种优化真的有用吗?

JavaFX团队的成员非常关注性能,因此他们肯定知道切换if和else对分支预测没有实质性影响。

我认为这表明重构没有显著的性能改进:

double minX = Double.MAX_VALUE;
double maxX = Double.MIN_VALUE;
for (int i = 0; i < children.size(); i++) {
  Node node = children.get(i);
  if (node.isManaged()) {
    final double x = node.getLayoutBounds().getMinX() + node.getLayoutX();
    minX = Math.min(minX, x);
    maxX = Math.max(maxX, x + node.minWidth(-1));
  }
}

因为分支预测本质上会把if/else变成类似的东西(给予或接受(。


这次提交证实了这一解释,尽管我不确定他们为什么要更改它——可能是因为当isManaged条件从未成立时,它会产生副作用。。。

进一步调查,提交指的是一个错误";Controls基准中高达50%的性能回归";(您需要登录才能访问它(。

他们似乎有一个性能问题,在调查时注意到代码中有一个错误(类似于我上面的例子(。他们修复了这个错误,并添加了一条注释,以澄清由于分支预测,修复不会影响性能。

摘录:

[..]查看代码时,我注意到在没有管理皮肤的子项的情况下出现了一个错误。在这种情况下,minX/Y和maxX/Y最终将成为MAX/MIN_VALUE,我们真正希望它们为零。因此,这个变更集解决了这个问题。在测试中,我看到性能有了一个小的改进(~1fps(,所以我认为这个更改并不能解决性能问题。无论如何,代码必须是原来的样子。

[…]请注意,在使用MIN/MAX时存在一个错误-这些值不是Float和Double的最大值和最小值(这对于积分类型的wrt对称性来说是有意义的,但这不是它们的指定方式(。对于对浮点值执行最小/最大运算,您希望使用负/正_INFINITY值来实现您想要的结果。

句子"分支预测支持最常用的条件"并没有说明被评估条件的值,无论是正数还是负数。它只是说分支预测可能有助于更频繁使用的条件分支,即循环中的条件分支。所以它基本上说,在循环中使用if是可以的

虽然结论是正确的,但你不应该担心循环中的ifs(除非剖析器告诉你有瓶颈,否则你不应该关心任何事情(,句子本身在Java环境中是非常没有意义的。

分支预测是CPU的一项功能,因此在解释执行中,它与Java级分支无关,因为它们只是修改解释器的状态(即读取下一条指令的指针(,但与特定分支的CPU指令无关。

一旦HotSpot出现,情况就完全不同了。如果这段代码是一个热点,优化器将应用大量的代码转换,这些转换呈现出关于Java代码将如何执行的大多数假设,都是错误的。

一个常见的优化是循环展开。不是用一块代码来表示循环的主体,而是将有多个实例相互跟随,并针对其特定迭代的不变量进行优化。这种设置允许完全消除与if相关的分支,因为完全可以预测,在firstManagedChildtruefalse的第一次转换之后,它将永远不会返回,因此,虽然第一次迭代总是看到它的true值,但可以优化后续迭代的代码,将变量视为恒定的false

因此,在这种情况下,分支预测将再次没有意义,因为对于其结果可以预先预测的if语句将没有分支。

在这里可以找到一篇关于分支预测驾驶室的非常详细的文章

回答你的问题——据我所知,没有。我认为否定"如果"不会有任何区别。它将优化重复假值的条件,就像优化多个真值一样。

除了现有答案之外:

这条评论似乎不是"否定if"的理由(因为无论如何,这不应该对性能产生影响(。相反,这可能是而不是尝试"优化"代码并避免单个if的理由。这可能有这样的东西:

protected double computeMinWidth(double height, double topInset, 
    double rightInset, double bottomInset, double leftInset) {
    double minX = 0;
    double maxX = 0;
    // Initialize minX/maxX with the coordinate of the FIRST managed child
    int i = 0;
    for (i = 0; i < children.size(); i++) {
        Node node = children.get(i);
        if (node.isManaged()) {
            final double x = node.getLayoutBounds().getMinX() + node.getLayoutX();
            minX = x;
            maxX = x + node.minWidth(-1);
        }
    }
    // Continue the loop at the last index, updating the
    // minX/maxX with the remaining managed children
    for (; i < children.size(); i++) {
        Node node = children.get(i);
        if (node.isManaged()) {
            final double x = node.getLayoutBounds().getMinX() + node.getLayoutX();
            // Don't have to check whether it's the first managed child here. 
            // Just do the update.
            minX = Math.min(minX, x);
            maxX = Math.max(maxX, x + node.minWidth(-1));
        }
    }
    double minWidth = maxX - minX;
    return leftInset + minWidth + rightInset;
}

注释指出,由于保存了if,这不会带来任何性能优势,因为由于分支预测,该if在实践中基本上是免费的。

旁注:我认为可能还有其他"微优化",比如避免Math#min(double,double)。但是JavaFX的人(希望(知道他们的东西,如果这可能会对他们的情况产生影响,他们可能会这么做(

相关内容

  • 没有找到相关文章

最新更新