了解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_1000MyBenchmark.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); } 

Risc管道

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:

软件流水线操作实际上与硬件流水线无关,是一种循环优化技术,可使迭代中的语句彼此独立。 目标是删除依赖关系,以便可以并行执行看似顺序的指令。

在这里查看更多:

  • 软件流水线解释

  • 软件流水线 – 维基百科