我是 Java 8 的新手。我仍然不深入了解 API,但我做了一个小的非正式基准测试来比较新的 Streams API 和旧的 Collections 的性能。
测试包括过滤 Integer
的列表,并为每个偶数计算平方根并将其存储在结果 List
的 Double
.
这是代码:
public static void main(String[] args) {
//Calculating square root of even numbers from 1 to N
int min = 1;
int max = 1000000;
List<Integer> sourceList = new ArrayList<>();
for (int i = min; i < max; i++) {
sourceList.add(i);
}
List<Double> result = new LinkedList<>();
//Collections approach
long t0 = System.nanoTime();
long elapsed = 0;
for (Integer i : sourceList) {
if(i % 2 == 0){
result.add(Math.sqrt(i));
}
}
elapsed = System.nanoTime() - t0;
System.out.printf("Collections: Elapsed time:\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));
//Stream approach
Stream<Integer> stream = sourceList.stream();
t0 = System.nanoTime();
result = stream.filter(i -> i%2 == 0).map(i -> Math.sqrt(i)).collect(Collectors.toList());
elapsed = System.nanoTime() - t0;
System.out.printf("Streams: Elapsed time:\t\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));
//Parallel stream approach
stream = sourceList.stream().parallel();
t0 = System.nanoTime();
result = stream.filter(i -> i%2 == 0).map(i -> Math.sqrt(i)).collect(Collectors.toList());
elapsed = System.nanoTime() - t0;
System.out.printf("Parallel streams: Elapsed time:\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));
}.
以下是双核机器的结果:
Collections: Elapsed time: 94338247 ns (0,094338 seconds)
Streams: Elapsed time: 201112924 ns (0,201113 seconds)
Parallel streams: Elapsed time: 357243629 ns (0,357244 seconds)
对于这个特定的测试,流的速度大约是集合的两倍,并且并行性没有帮助(或者我使用它的方式不对?)。
问题:
- 这个考试公平吗?我犯了什么错误吗?
- 流比集合慢吗?有没有人对此做出良好的正式基准?
- 我应该努力采用哪种方法?
更新结果。
按照@pveentjer 的建议,我在 JVM 预热(1k 次迭代)后运行了 1k 次测试:
Collections: Average time: 206884437,000000 ns (0,206884 seconds)
Streams: Average time: 98366725,000000 ns (0,098367 seconds)
Parallel streams: Average time: 167703705,000000 ns (0,167704 seconds)
在这种情况下,流的性能更高。我想知道在运行时仅调用一次或两次过滤功能的应用程序中会观察到什么。
原文由 Mister Smith 发布,翻译遵循 CC BY-SA 4.0 许可协议
停止使用
LinkedList
除了使用迭代器从列表中间大量删除之外的任何内容。停止手动编写基准测试代码,使用 JMH 。
适当的基准:
结果:
正如我预期的那样,流实现相当慢。 JIT 能够内联所有 lambda 的东西,但不会产生像 vanilla 版本那样完美简洁的代码。
通常,Java 8 流并不神奇。他们无法加速已经很好实施的事情(可能是简单的迭代或 Java 5 的 for-each 语句替换为
Iterable.forEach()
和Collection.removeIf()
调用)。流更多的是关于编码的便利性和安全性。便利——速度权衡在这里起作用。