了解jvm中的循环性能

St.*_*rio 12 java performance loops jmh

我正在玩,jmh在关于循环的部分他们说

您可能会注意到重复次数越多,所测量操作的"感知"成本就越低.到目前为止,我们每增加1/20 ns,远远超出硬件实际可行的范围.发生这种情况是因为循环被大量展开/流水线化,并且要从循环中提升要测量的操作.士气:不要过度使用循环,依靠JMH来获得正确的测量.

我亲自尝试过

    @Benchmark
    @OperationsPerInvocation(1)
    public int measurewrong_1() {
        return reps(1);
    }      

    @Benchmark
    @OperationsPerInvocation(1000)
    public int measurewrong_1000() {
        return reps(1000);
    }      
Run Code Online (Sandbox Code Playgroud)

得到以下结果:

Benchmark                      Mode  Cnt  Score    Error  Units
MyBenchmark.measurewrong_1     avgt   15  2.425 ±  0.137  ns/op
MyBenchmark.measurewrong_1000  avgt   15  0.036 ±  0.001  ns/op
Run Code Online (Sandbox Code Playgroud)

它确实表明它MyBenchmark.measurewrong_1000比它快得多MyBenchmark.measurewrong_1.但我无法真正理解JVM在提高性能方面所做的优化.

他们的意思是循环展开/流水线

die*_*ter 8

循环展开使流水线成为可能.因此,可管道的CPU(例如RISC)可以并行执行展开的代码.

因此,如果您的CPU能够并行执行5个管道,那么您的循环将以以下方式展开:

// pseudo code
int pipelines = 5;
for(int i = 0; i < length; i += pipelines){
    s += (x + y);
    s += (x + y);
    s += (x + y);
    s += (x + y);
    s += (x + y);
}
Run Code Online (Sandbox Code Playgroud)

Risc管道

IF =指令获取,ID =指令解码,EX =执行,MEM =存储器访问,WB =寄存器写回

来自Oracle白皮书:

...标准编译器优化,可实现更快的循环执行.循环展开增加了循环体尺寸,同时减少了迭代次数.循环展开还可以提高其他优化的效率.

有关管道传输的更多信息:经典RISC管道


apa*_*gin 6

循环展开是一种技术,通过重复循环体来展平多个循环迭代.
例如,在给定的例子中

    for (int i = 0; i < reps; i++) {
        s += (x + y);
    }
Run Code Online (Sandbox Code Playgroud)

可以通过JIT编译器将其展开为类似的东西

    for (int i = 0; i < reps - 15; i += 16) {
        s += (x + y);
        s += (x + y);
        // ... 16 times ...
        s += (x + y);
    }
Run Code Online (Sandbox Code Playgroud)

然后可以进一步优化扩展的循环体

    for (int i = 0; i < reps - 15; i += 16) {
        s += 16 * (x + y);
    }
Run Code Online (Sandbox Code Playgroud)

显然,计算16 * (x + y)速度比计算速度快(x + y)16倍.