现象引入
下面看一个循环的两种写法。之前从同事那里听到一个说法是,下面这种方式在执行循环过程中,在方法栈帧中只会创建一个局部变量,比方式一会比较省时间。
// 方式一: 变量放在循环体里面
for (int i = 0; i < count; i++) {
Object obj = new Object();
}
// 方式二: 变量放在循环体外面
Object obj;
for (int i = 0; i < count; i++) {
obj = new Object();
}
下面就来验证一下
测试案例
本文测试使用的jdk版本是jdk1.8.0_361
➜ java -version
java version "1.8.0_361"
Java(TM) SE Runtime Environment (build 1.8.0_361-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.361-b09, mixed mode)
以下面这个代码为例
public class ForeachTest {
final int count = 100000;
public void inner() {
List<Object> list = new ArrayList<>();
for (int i = 0; i < count; i++) {
Object obj = new Object();
list.add(obj);
}
}
public void outer() {
List<Object> list = new ArrayList<>();
Object obj;
for (int i = 0; i < count; i++) {
obj = new Object();
list.add(obj);
}
}
}
测试几次的结果如下:
CPU 12th Gen Intel® Core™ i7-12700H 基准速度: 2.30 GHz
每个循环次数都跑了几次,取的平均值,结果如下:
循环次数 | inner | outer |
---|---|---|
100 * 1000 | 3ms | 3ms |
1000 * 1000 | 16ms | 16ms |
10 * 1000 * 1000 | 2987ms | 297ms |
100 * 1000 * 1000 | 28860ms | 40446ms |
Benchmark
下面是Benchmark所用的测试代码,主要测试执行的平均时间这个指标
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.results.format.ResultFormatType;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Benchmark)
@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(2)
public class JMHExample_Foreach {
final int count = 1000000;
@Benchmark
public void inner() {
List<Object> list = new ArrayList<>();
for (int i = 0; i < count; i++) {
Object obj = new Object();
list.add(obj);
}
}
@Benchmark
public void outer() {
List<Object> list = new ArrayList<>();
Object obj;
for (int i = 0; i < count; i++) {
obj = new Object();
list.add(obj);
}
}
public static void main(String[] args) throws Exception {
Options opts = new OptionsBuilder()
.include(JMHExample_Foreach.class.getSimpleName()) // 指定测试的类
.resultFormat(ResultFormatType.JSON)
.build();
new Runner(opts).run();
}
}
下面是测试结果
-
循环100 *1000次
Benchmark Mode Cnt Score Error Units
JMHExample_Foreach.inner avgt 10 0.333 ± 0.037 ms/op
JMHExample_Foreach.outer avgt 10 0.334 ± 0.040 ms/op
-
循环1000 * 1000次
Benchmark Mode Cnt Score Error Units
JMHExample_Foreach.inner avgt 10 3.865 ± 0.036 ms/op
JMHExample_Foreach.outer avgt 10 5.949 ± 2.471 ms/op
-
循环10 * 1000 * 1000次
Benchmark Mode Cnt Score Error Units
JMHExample_Foreach.inner avgt 10 57.637 ± 4.896 ms/op
JMHExample_Foreach.outer avgt 10 95.938 ± 27.384 ms/op
-
循环100 * 1000 * 1000次
Benchmark Mode Cnt Score Error Units
JMHExample_Foreach.inner avgt 10 20455.711 ± 17706.877 ms/op
JMHExample_Foreach.outer avgt 10 23232.470 ± 21912.028 ms/op
现象分析
实际上这两个方法编译过后的结果都是一样的,下面是通过intellij反编译的结果
字节码对比
两个方法的字节码其实也差不多,从前面反编译的结果就能看出来了
inner方法
0 new #4 <java/util/ArrayList>
3 dup
4 invokespecial #5 <java/util/ArrayList.<init> : ()V>
7 astore_1
8 iconst_0
9 istore_2
10 iload_2
11 ldc #2 <100000000>
13 if_icmpge 38 (+25)
16 new #7 <java/lang/Object>
19 dup
20 invokespecial #1 <java/lang/Object.<init> : ()V>
23 astore_3
24 aload_1
25 aload_3
26 invokeinterface #8 <java/util/List.add : (Ljava/lang/Object;)Z> count 2
31 pop
32 iinc 2 by 1
35 goto 10 (-25)
38 return
outer
0 new #4 <java/util/ArrayList>
3 dup
4 invokespecial #5 <java/util/ArrayList.<init> : ()V>
7 astore_1
8 iconst_0
9 istore_3
10 iload_3
11 ldc #2 <100000000>
13 if_icmpge 38 (+25)
16 new #7 <java/lang/Object>
19 dup
20 invokespecial #1 <java/lang/Object.<init> : ()V>
23 astore_2
24 aload_1
25 aload_2
26 invokeinterface #8 <java/util/List.add : (Ljava/lang/Object;)Z> count 2
31 pop
32 iinc 3 by 1
35 goto 10 (-25)
38 return