问题
I heard that Java supports "Loop Unswitching", so I simply tested it in JMH.
I thought they would be exactly the same after JIT. Why is this?
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);
}
}
}
Test Results
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
Test Environment
# JMH version: 1.21
# VM version: JDK 1.8.0_222, OpenJDK 64-Bit Server VM, 25.222-b10
回答1:
JMH disables inlining of Blackhole.consume
method. A non-inlined method is a black box to the JVM - the compiler does not know whether such method modifies fields, throws exceptions, which registers it trashes etc. JIT compiler cannot apply many optimizations across such method call. (Imagine that a black box method uses Reflection to modify bool
field, and thus loop unswitching will become invalid).
HotSpot JVM still supports loop unswitching when the compilation scope includes the whole loop body, and the condition is known to be constant throughout the loop.
Consider the modified benchmark:
@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;
}
}
In this case the performance of test1
and test2
will be similar:
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
来源:https://stackoverflow.com/questions/59114891/loop-unswitching-optimization-is-not-working