"Loop unswitching" 优化无效
"Loop unswitching" optimization is not working
听说Java支持"Loop Unswitching",所以就简单在JMH里测试了一下
我以为他们在 JIT 之后会完全一样。这是为什么?
private final int TIMES = 1_000_000;
private boolean bool;
private Random r = new Random(93);
@Setup(Level.Invocation)
public void fresh() {
bool = r.nextBoolean();
}
@Benchmark
public void test1(Blackhole bh) {
for (int i = 0; i < TIMES; i++) {
if (bool) {
bh.consume(1);
} else {
bh.consume(2);
}
}
}
@Benchmark
public void test2(Blackhole bh) {
if (bool) {
for (int i = 0; i < TIMES; i++) {
bh.consume(1);
}
} else {
for (int i = 0; i < TIMES; i++) {
bh.consume(2);
}
}
}
测试结果
Benchmark Mode Cnt Score Error Units
LoopUnswitching.test1 avgt 25 1995.192 ± 3.497 us/op
LoopUnswitching.test2 avgt 25 1644.951 ± 4.904 us/op
测试环境
# JMH version: 1.21
# VM version: JDK 1.8.0_222, OpenJDK 64-Bit Server VM, 25.222-b10
JMH 禁用 Blackhole.consume
方法的内联。非内联方法对于 JVM 来说是一个黑盒子——编译器不知道此类方法是否修改字段、抛出异常、将其注册为垃圾等。JIT 编译器无法在此类方法调用中应用许多优化。 (假设一个黑盒方法使用Reflection修改bool
字段,这样loop unswitching就失效了)
HotSpot JVM 仍然支持当编译范围包括整个循环体时循环取消切换,并且已知条件在整个循环中保持不变。
考虑修改后的基准:
@State(Scope.Benchmark)
public class LoopUnswitching {
private static final int TIMES = 10_000;
private final Random r = new Random(93);
private final int[] x = r.ints(TIMES).toArray();
private final int[] y = r.ints(TIMES).toArray();
private boolean bool;
@Setup(Level.Invocation)
public void setup() {
bool = r.nextBoolean();
}
@Benchmark
public int test1() {
int sum = 0;
for (int i = 0; i < TIMES; i++) {
if (bool) {
sum += x[i];
} else {
sum += y[i];
}
}
return sum;
}
@Benchmark
public int test2() {
int sum = 0;
if (bool) {
for (int i = 0; i < TIMES; i++) {
sum += x[i];
}
} else {
for (int i = 0; i < TIMES; i++) {
sum += y[i];
}
}
return sum;
}
}
在这种情况下,test1
和 test2
的性能相似:
Benchmark Mode Cnt Score Error Units
LoopUnswitching.test1 avgt 10 2910,432 ± 3,287 ns/op
LoopUnswitching.test2 avgt 10 2912,922 ± 9,367 ns/op
听说Java支持"Loop Unswitching",所以就简单在JMH里测试了一下
我以为他们在 JIT 之后会完全一样。这是为什么?
private final int TIMES = 1_000_000;
private boolean bool;
private Random r = new Random(93);
@Setup(Level.Invocation)
public void fresh() {
bool = r.nextBoolean();
}
@Benchmark
public void test1(Blackhole bh) {
for (int i = 0; i < TIMES; i++) {
if (bool) {
bh.consume(1);
} else {
bh.consume(2);
}
}
}
@Benchmark
public void test2(Blackhole bh) {
if (bool) {
for (int i = 0; i < TIMES; i++) {
bh.consume(1);
}
} else {
for (int i = 0; i < TIMES; i++) {
bh.consume(2);
}
}
}
测试结果
Benchmark Mode Cnt Score Error Units
LoopUnswitching.test1 avgt 25 1995.192 ± 3.497 us/op
LoopUnswitching.test2 avgt 25 1644.951 ± 4.904 us/op
测试环境
# JMH version: 1.21
# VM version: JDK 1.8.0_222, OpenJDK 64-Bit Server VM, 25.222-b10
JMH 禁用 Blackhole.consume
方法的内联。非内联方法对于 JVM 来说是一个黑盒子——编译器不知道此类方法是否修改字段、抛出异常、将其注册为垃圾等。JIT 编译器无法在此类方法调用中应用许多优化。 (假设一个黑盒方法使用Reflection修改bool
字段,这样loop unswitching就失效了)
HotSpot JVM 仍然支持当编译范围包括整个循环体时循环取消切换,并且已知条件在整个循环中保持不变。
考虑修改后的基准:
@State(Scope.Benchmark)
public class LoopUnswitching {
private static final int TIMES = 10_000;
private final Random r = new Random(93);
private final int[] x = r.ints(TIMES).toArray();
private final int[] y = r.ints(TIMES).toArray();
private boolean bool;
@Setup(Level.Invocation)
public void setup() {
bool = r.nextBoolean();
}
@Benchmark
public int test1() {
int sum = 0;
for (int i = 0; i < TIMES; i++) {
if (bool) {
sum += x[i];
} else {
sum += y[i];
}
}
return sum;
}
@Benchmark
public int test2() {
int sum = 0;
if (bool) {
for (int i = 0; i < TIMES; i++) {
sum += x[i];
}
} else {
for (int i = 0; i < TIMES; i++) {
sum += y[i];
}
}
return sum;
}
}
在这种情况下,test1
和 test2
的性能相似:
Benchmark Mode Cnt Score Error Units
LoopUnswitching.test1 avgt 10 2910,432 ± 3,287 ns/op
LoopUnswitching.test2 avgt 10 2912,922 ± 9,367 ns/op