“循环取消切换"指的是“循环切换".优化不起作用

“循环取消切换

问题描述:

我听说Java支持循环取消切换",所以我只是在JMH中对其进行了测试.

I heard that Java supports "Loop Unswitching", so I simply tested it in JMH.

我认为在JIT之后它们将完全相同.为什么会这样?

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);
        }
    }
}

测试结果

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编译器无法在此类方法调用中应用许多优化. (想象一下,黑盒方法使用反射来修改bool字段,因此循环取消切换将无效.)

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仍支持循环取消切换,并且条件在整个循环中都是恒定的.

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.

考虑修改后的基准:

@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;
    }
}

在这种情况下,test1test2的性能将相似:

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