选项 1:
boolean isFirst = true;
for (CardType cardType : cardTypes) {
if (!isFirst) {
descriptionBuilder.append(" or ");
} else {
isFirst = false;
}
//other code not relevant to this theoretical question
}
选项 2:
boolean isFirst = true;
for (CardType cardType : cardTypes) {
if (!isFirst) {
descriptionBuilder.append(" or ");
}
isFirst = false;
//other code not relevant to this theoretical question
}
我的分析:两个代码具有相同的语义。
第一个代码)我不确定这段代码是有两个分支(就分支预测器而言)还是一个分支。我正在研究 http://en.wikipedia.org/wiki/X86_instruction_listings 但无法弄清楚有一个 X86 指令,例如"如果以前的条件值是错误的跳转那里",以避免两个分支预测(非常糟糕)
第二个代码)最有可能总是执行简单的MOV(寄存器或元素很可能已经在缓存中),这相对便宜(最多几个周期)
所以,我的观点是,除非处理器解码成微码指令可以做一些聪明的事情,或者X86指令存在以避免必要的分支预测,否则第二码更快。
我知道这纯粹是理论问题,因为在实践中,这个分支可以使应用程序快 0.000000002% 或类似的东西。
我错过了什么吗?
编辑:我添加了一个循环,用于为有问题的分支提供更多"权重"
编辑2:问题是关于用于分支预测的英特尔架构(奔腾和更新的处理器)。
使用 JMH 给出以下数字,大小为 10 的 cardTypes 数组和整数增量作为逻辑(Java 15/AMD 3950X/Windows 10):
Benchmark Mode Cnt Score Error Units
Benchmark.option1 thrpt 25 273369417.720 ± 1618952.179 ops/s
Benchmark.option2 thrpt 25 273415784.192 ± 852618.585 ops/s
"选项 2"的平均性能提高了约 0.017% (YMMV)。
另请参阅:分支预测、方法调度、内存访问、吞吐量和延迟、垃圾回收。
代码具有相同的效果,但不会产生相同的字节代码或程序集(可能)。
这在性能方面有多大差异尚不清楚,并且可能是微不足道的。
更重要的是代码的清晰度。 由于在这样的简单情况下,由于代码更难推理,我看到了更多的错误和性能问题。
简而言之,对你来说最清晰和最简单的东西也可能足够快,或者最容易修复。
不同的硬件对每个汇编程序指令都有不同的成本,在现代硬件上,由于流水线和缓存的影响,甚至指令的成本也很难预测。
从您的孤立示例中不清楚流水线和缓存上的 if 和 if/else 之间的区别。 如果您运行过一次该代码,则根本不可能看到任何差异。 在紧密循环中反复运行它,if 本身的性能将由 a) 检查成本和 b) 检查结果的可预测性主导。 换句话说,分支预测将成为主导因素,并且不会受到if或if/else代码块的影响。
关于分支预测效果的精彩讨论可以在这里阅读 为什么处理排序数组比处理未排序数组更快?(请参阅最高评分答案)。
假设您的代码片段是 for 循环中的 if 块。 Hotspot 具有展开循环的能力,这包括进行常见的"是循环的第一次迭代"检查并将其内联到循环之外。 从而避免了在每次循环迭代时重新检查条件的成本。 从而避免关注哪个更快,如果或如果/否则。
Oracle 在此处记录了此行为
两个代码具有相同的语义。
否 两个代码不同,
第一个代码应用isFirst = false;
将标志设置为 false,如果你的条件不匹配if (!isFirst)
。
第二次代码每次更改您的标志false
甚至满足条件。
if/else
构造中有两个分支:顶部的条件分支和if
部分末尾的else
部分周围的分支。else
部分没有分支,至少在任何中等能力的编译器中都没有分支。
与此相反,您必须平衡始终执行isFirst = false;
行的成本。
在您提到的特定情况下,与方法调用的成本相比,它不太可能产生丝毫差异。