Java 8中流和闭包的成本

时间:2014-12-31 02:12:18

标签: java lambda java-8 java-stream

我正在重写一个涉及使用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 vs. 5 ms。

如果我直接使用forEach设置,即set.forEach(),则结果为:12 vs 5ms。

最后,如果我在外面创建一次关闭

Consumer<? super String> consumer = s -> System.out.println(s);

并使用set.forEach(c)结果将是7 vs 5 ms。

当然,nubmers很小,我的基准测试非常原始,但这个例子是否表明初始化流和闭包有一些开销?

(实际上,由于set为空,因此闭包的初始化成本在这种情况下不应该很重要,但是,如果我考虑先手而不是动态创建闭包,那么

1 个答案:

答案 0 :(得分:7)

您在此处看到的费用根本不与“关闭”相关联,而是与Stream初始化的费用相关联。

我们来看看你的三个示例代码:

for (int i = 0; i < 10_000_000; i++) {
    Set<String> set = Collections.emptySet();
    set.stream().forEach(s -> System.out.println(s));
}

这个在每个循环中创建一个 new 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的文献。