jvm 在java?中优化循环内的空值检查

u91tlkcl  于 2022-11-07  发布在  Java
关注(0)|答案(3)|浏览(102)

我正在写一个效率非常重要的算法。有时候我也想通过调用一些“回调”函数来跟踪算法的行为。让我们假设我的代码看起来像这样:

public float myAlgorithm(AlgorithmTracker tracker) {
    while (something) { // millions of iterations
        doStuff();
        if (tracker != null) tracker.incrementIterationCount(); // <--- How to run the if only once?
        doOtherStaff();
    }
}

如何防止执行if语句一百万次?编译器是否发现tracker从未被重新赋值?如果第一次检查时它为null,则它将始终为null。如果不是,则它将永远不会为null。
理想情况下,我希望告诉编译器以这样一种方式构建代码,即如果tracker为null(在运行时),它将以与

while (something) { // millions of iterations
        doStuff();
        doOtherStaff();
    }

我想到了两个解决办法:

  • 我可以编写两个版本的myAlgorithm,一个有调用,一个没有调用,但这会导致大量的代码重复。
  • 我可以将AlgorithmTracker提取到一个接口中,并创建一个带有空函数的伪空tracker。不过,我不知道编译器是否会优化这些调用。
lsmd5eda

lsmd5eda1#

对于大多数CPU架构,您不必担心要应用的优化,因为该特定优化是大多数当代CPU的一部分。它被称为分支预测,当前的CPU非常擅长于此。
平均而言,CPU执行的每第6条指令是一个分支,并且如果对于每个分支,CPU必须等待并评估分支条件,则这将使执行慢很多。

分支预测和推测执行

因此,当面临分支时,CPU在不评估分支条件的情况下开始执行(* 推测执行 *)它认为很可能是正确的路径,并且在稍后阶段,当分支条件的结果变得可用时,CPU检查它是否正在执行正确的路径。
如果CPU选择的路径与分支条件的结果一致,则CPU知道它正在执行正确的路径,因此它保持100%的速度,否则它将不得不清除它推测性地执行的所有指令,并从正确的路径开始。

但是CPU如何知道选择哪个路径?

进入CPU的分支预测器子系统。在其最基本的形式中,它将存储有关分支过去行为的信息,例如,如果一个分支在一段时间内没有被选取,那么它很可能现在也不会被选取。这是一个简单的解释,而真实的的分支预测器将是相当复杂的。

那么,这些分支预测器的有效性如何呢?

假设分支预测器的核心只是模式匹配机器,如果分支显示可预测的模式,那么您可以放心,分支预测器会正确执行。但如果分支根本没有显示模式,那么分支预测器就不会帮助您,最糟糕的是,它会因为所有错误的预测而妨碍代码执行。

您的代码将如何使用分支预测器?

在你的例子中,分支控制变量的值永远不会改变,所以分支要么在循环的每次迭代中都被选择,要么永远不会被选择。这清楚地表明了一种模式,即使是最基本的分支预测器也能识别。这意味着你的代码实际上会像条件不存在一样执行。因为在最初的几次迭代之后,分支预测器将能够以100%的准确度挑选路径。
要了解更多,请阅读this great so thread

**有趣的事实:**这种特定的优化是导致CPU漏洞(如幽灵和崩溃)的原因

vcirk6k6

vcirk6k62#

大量代码重复
大量的代码重复意味着有大量的代码,那么一个简单的空值检查如何影响性能呢?
从循环中提升空值检查是一个非常简单的优化。不能保证它能完成,但是当JIT不能做到时,CPU仍然可以完美地预测分支结果。()因此,分支将花费大约¼个周期,因为当前的CPU能够在每个周期执行4条指令。
正如已经说过的,由于JIT必须进行空值检查,所以无论如何都会有一些分支,所以最有可能的是,这种过早优化的净收益为零。
)预测可能会被大量的其他分支从预测器中驱逐出来(类似于缓存),但你的糟糕分支只是众多分支中的一个,你不必在意。

l7mqbcuq

l7mqbcuq3#

我可以编写两个版本的myAlgorithm......但这会导致大量代码重复”
是的,这可能是一种优化性能的方法,也是 DRY 不起作用的罕见情况之一。这类 RY 技术的另一个例子--循环展开(如果你的编译器没有这样做:))。在这里,代码重复是你为获得更好的性能所付出的代价。

但是,对于您的特定IF,您可以看到,循环中的条件没有改变,CPU分支预测应该工作得很好。进行良好/正确的性能测试(与JMH,例如)和一些东西告诉我,你不会看到任何差异与这样的皮科(即使不是微观的)优化,结果可能会更糟,因为还有更重要的事情可能会影响整体性能。下面是一些这样的事情:

  • 最有效的编译器优化是内联(https://www.baeldung.com/jvm-method-inlining)。如果您的代码转换阻碍了内联,请三思而后行,并正确地衡量结果性能
  • 内存分配,因此,GC在应用程序的主要/关键路径上的暂停也可能是一件重要的事情。2如果需要的话,重用可变对象(池)。
  • 缓存未命中。确保尽可能多地按顺序访问内存。一个典型的例子--用ArrayList替换LinkedList来迭代,性能会变得更好
  • 等等等等。

所以,不要担心这个特殊的IF在所有
性能优化是一个非常大而且非常有趣的领域。注意正确的事情,使使使使正确的性能测试......并且总是考虑合适的算法/集合,记住经典的大O。

相关问题