在for循环中向上或向下计数是最有效的迭代方式吗? 有时答案既不是。 阅读这篇文章,了解不同迭代品种的影响。
迭代性能
关于如何以高性能进行迭代有很多观点。 Java中的传统迭代方式是一个for循环,该循环从零开始,然后计数到一些预定义的数字:
private static final int ITERATIONS = 10_000;@Benchmark
public int forUp() {int sum = 0;for (int i = 0; i < ITERATIONS; i++) {sum += i;}return sum;
}
有时,我们遇到一个for循环,该循环以预定的非负值开始,然后递减计数。 这在JDK本身中非常普遍,例如在String
类中。 这是通过递减而不是递增来解决先前问题的示例。
@Benchmark
public int forDown() {int sum = 0;for (int i = ITERATIONS; i-- > 0;) {sum += i;}return sum;
}
我认为,这样做的理由是,检查值与零的关系可能比测试值与任何其他任意值的关系更有效。 实际上,我所知道的所有CPU都有机器码指令,可以检查给定值与零的关系。 另一个想法是,上面给出的递减计数习惯似乎只检查一次循环变量(它同时检查值,然后减小它),而不是顶部的常规示例。 我怀疑这对当今高效的JIT编译器影响很小或没有影响,后者将能够像优化第二个迭代一样优化第一个迭代。 当代码在解释模式下运行时可能会产生影响,但是本文中未对此进行检查。
另一种方法是使用
IntStream
看起来像这样:
@Benchmark
public int stream() {return IntStream.range(0, ITERATIONS).sum();
}
如果大型迭代需要更高的性能,则只需在流中添加.parallel()
运算符就可以使流并行变得相对容易。 本文未对此进行检查。
Graal VM下的性能
在我的笔记本电脑(MacBook Pro,2015年中,2.2 GHz Intel Core i7)上的GraalVM(rc-11,以及GraallVM附带的新C2编译器)下运行这些测试可以得出以下结果:
Benchmark Mode Cnt Score Error Units
ForBenchmark.forDown thrpt 5 311419.166 ± 4201.724 ops/s
ForBenchmark.forUp thrpt 5 309598.916 ± 12998.579 ops/s
ForBenchmark.stream thrpt 5 312360.089 ± 8291.792 ops/s
对于流解决方案是最快的解决方案(尽管误差在误差范围内),这可能会让某些人感到意外。
在上一篇文章中 ,我介绍了与传统命令式代码相比,流和声明式编程在代码度量方面的一些优势。 我尚未测试过冷代码段的性能(即,在JIT启动之前)。
聪明的数学
根据数学,我们记得从零开始的连续数字的总和为N *(N + 1)/ 2,其中N是序列中的最高数字。 运行此基准测试:
@Benchmark
public int math() {return ITERATIONS * (ITERATIONS + 1) / 2;
}
使我们的性能比以前的实现提高了1000倍以上:
Benchmark Mode Cnt Score Error Units
ForBenchmark.math thrpt 5 395561077.984 ± 11138012.141 ops/s
迭代次数越多,收益越大。 聪明有时会胜过蛮力。
超快速数据流
使用Speedment HyperStream,可以从数据库中获得类似的性能。 在HyperStream上信息 。
结论
在某些常用的硬件/ JVM上,无论我们在for循环中向上还是向下进行迭代都没有关系。 较新的JVM能够优化流迭代,因此与for循环相比,它们具有同等甚至更好的性能。
在我看来,与for循环相比,流代码通常更具可读性,因此,我相信流在某些将来可能是事实上的迭代发明。
使用Speedment HyperStream可以高性能地流传输数据库内容。
翻译自: https://www.javacodegeeks.com/2019/09/java-performance-for-eaching-vs-streaming.html