了解jvm中的循环性能
我正在玩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); }
得到以下结果:
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
它确实表明MyBenchmark.measurewrong_1000
比MyBenchmark.measurewrong_1
。 但我无法真正理解JVM的优化,以提高性能。
他们的意思是循环展开/流水线 ?
循环展开使流水线成为可能。 因此,可管道的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); }
IF =指令获取,ID =指令解码,EX =执行,MEM =存储器访问,WB =寄存器写回
来自Oracle白皮书 :
...标准编译器优化,可实现更快的循环执行。 循环展开增加了循环体尺寸,同时减少了迭代次数。 循环展开还可以提高其他优化的效率。
有关管道传输的更多信息: 经典RISC管道
循环展开是一种技术,通过重复循环体来展平多个循环迭代。
例如在给定的例子中
for (int i = 0; i < reps; i++) { s += (x + y); }
可以通过JIT编译器将其展开为类似的东西
for (int i = 0; i < reps - 15; i += 16) { s += (x + y); s += (x + y); // ... 16 times ... s += (x + y); }
然后可以进一步优化扩展的循环体
for (int i = 0; i < reps - 15; i += 16) { s += 16 * (x + y); }
显然,计算16 * (x + y)
比计算(x + y)
快16倍。
循环流水线=软件流水线。
基本上,它是一种用于优化顺序循环迭代效率的技术,通过执行循环体中的一些指令 – 并行 。
当然,这只能在满足某些条件时才能完成,例如每次迭代都不依赖于其他条件等。
来自insidehpc.com:
软件流水线操作实际上与硬件流水线无关,是一种循环优化技术,可使迭代中的语句彼此独立。 目标是删除依赖关系,以便可以并行执行看似顺序的指令。
在这里查看更多:
-
软件流水线解释
-
软件流水线 – 维基百科