为什么两个单独的循环比一个快?
我想了解Java对连续的for循环进行了什么样的优化。更准确地说,我正在尝试检查是否执行了循环融合。从理论上讲,我期望这种优化不是自动完成的,并且期望确认融合版本比具有两个循环的版本更快。
但是,在运行基准测试后,结果表明两个单独的(和连续的)循环比一个完成所有工作的单个循环更快。
我已经尝试使用JMH来创建基准测试,并得到了相同的结果。
我使用了该命令,它显示为具有两个循环的源文件生成的字节码实际上对应于正在执行的两个循环(没有执行循环展开或其他优化)。javap
正在测量的代码:BenchmarkMultipleLoops.java
private void work() {
List<Capsule> intermediate = new ArrayList<>();
List<String> res = new ArrayList<>();
int totalLength = 0;
for (Capsule c : caps) {
if(c.getNumber() > 100000000){
intermediate.add(c);
}
}
for (Capsule c : intermediate) {
String s = "new_word" + c.getNumber();
res.add(s);
}
//Loop to assure the end result (res) is used for something
for(String s : res){
totalLength += s.length();
}
System.out.println(totalLength);
}
正在测量的代码:BenchmarkSingleLoop.java
private void work(){
List<String> res = new ArrayList<>();
int totalLength = 0;
for (Capsule c : caps) {
if(c.getNumber() > 100000000){
String s = "new_word" + c.getNumber();
res.add(s);
}
}
//Loop to assure the end result (res) is used for something
for(String s : res){
totalLength += s.length();
}
System.out.println(totalLength);
}
这是代码:Capsule.java
public class Capsule {
private int number;
private String word;
public Capsule(int number, String word) {
this.number = number;
this.word = word;
}
public int getNumber() {
return number;
}
@Override
public String toString() {
return "{" + number +
", " + word + '}';
}
}
caps
是一个包含 2000 万个元素的,一开始是这样填充的:ArrayList<Capsule>
private void populate() {
Random r = new Random(3);
for(int n = 0; n < POPSIZE; n++){
int randomN = r.nextInt();
Capsule c = new Capsule(randomN, "word" + randomN);
caps.add(c);
}
}
在测量之前,执行预热阶段。
我运行了每个基准测试10次,换句话说,该方法为每个基准测试执行10次,完成的平均时间如下所示(以秒为单位)。每次迭代后,都会执行 GC 以及一些休眠:work()
- 多回路:4.9661 秒
- 单回点:7.2725秒
OpenJDK 1.8.0_144 在 Intel i7-7500U(Kaby Lake)上运行。
为什么MultiLoops版本比SingleLoop版本更快,即使它必须遍历两个不同的数据结构?
更新 1:
正如注释中建议的那样,如果我更改实现以计算生成字符串的时间,从而避免创建列表,则单循环版本会变得更快。totalLength
res
但是,引入该变量只是为了在创建结果列表后完成一些工作,以避免在未对元素执行任何操作时丢弃这些元素。
换句话说,预期的结果是生成最终列表。但这个建议有助于更好地理解正在发生的事情。
结果:
- 多回点:0.9339 秒
- 单回点:0.66590005秒
更新 2:
以下是我用于 JMH 基准测试的代码的链接:https://gist.github.com/FranciscoRibeiro/2d3928761f76e4f7cecfcfcdf7fc96d5
结果:
- 多回点:7.397 秒
- 单循环:8.092秒