我正在重写一个涉及使用 Java 8 处理 1000 万个对象的应用程序,我注意到流可以使应用程序的速度减慢 25%。有趣的是,当我的集合为空时也会发生这种情况,因此这是流的恒定初始化时间。要重现该问题,请考虑以下代码:
long start = System.nanoTime();
for (int i = 0; i < 10_000_000; i++) {
Set<String> set = Collections.emptySet();
set.stream().forEach(s -> System.out.println(s));
}
long end = System.nanoTime();
System.out.println((end - start)/1000_000);
start = System.nanoTime();
for (int i = 0; i < 10_000_000; i++) {
Set<String> set = Collections.emptySet();
for (String s : set) {
System.out.println(s);
}
}
end = System.nanoTime();
System.out.println((end - start)/1000_000);
结果如下:224 对 5 毫秒。
如果我直接在现场使用
forEach
,即 set.forEach()
,结果将是:12 vs 5ms。最后,如果我在外面创建一次闭包
Consumer<? super String> consumer = s -> System.out.println(s);
并使用
set.forEach(c)
结果将是 7 对 5 毫秒。当然,数字很小,我的基准测试非常原始,但是这个例子是否表明初始化流和闭包有开销?
(实际上,由于
set
是空的,在这种情况下,闭包的初始化成本应该不重要,但是,我应该考虑事先创建闭包而不是即时创建闭包) 最佳答案
您在此处看到的成本根本与“闭包”无关,而是与 Stream
初始化的成本有关。
让我们拿你的三个示例代码:
for (int i = 0; i < 10_000_000; i++) {
Set<String> set = Collections.emptySet();
set.stream().forEach(s -> System.out.println(s));
}
这会在每个循环中创建一个新的
Stream
实例;至少对于前 10k 次迭代,请参见下文。在 10k 次迭代之后,好吧,JIT 可能足够聪明,可以看出它无论如何都是空操作。for (int i = 0; i < 10_000_000; i++) {
Set<String> set = Collections.emptySet();
for (String s : set) {
System.out.println(s);
}
}
这里 JIT 又开始了:空集?好吧,这是一个无操作的故事结束。
set.forEach(System.out::println);
为集合创建了一个
Iterator
,它总是空的?同样的故事,JIT 开始了。你的代码一开始的问题是你没有考虑到 JIT;对于实际测量,在测量之前至少运行 10k 次循环,因为 10k 次执行是 JIT 启动所需的(至少,HotSpot 是这样操作的)。
现在,lambdas:它们是调用站点,它们只链接一次;但当然,初始链接的成本仍然存在,并且在您的循环中,您包括此成本。在进行测量之前尝试只运行一个循环,这样就不会产生这种成本。
总而言之,这不是一个有效的微基准测试。使用 caliper 或 jmh 来真正衡量性能。
一个很好的视频,用于了解 lambdas 如何工作 here 。现在它有点老了,JVM 比使用 lambdas 时要好得多。
如果您想了解更多信息,请查找有关
invokedynamic
的文献。关于java - Java 8 中流和闭包的成本,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/27715295/