Java8中流和闭包的成本

Java8中流和闭包的成本,java,lambda,java-8,java-stream,Java,Lambda,Java 8,Java Stream,我正在重写一个应用程序,它涉及到使用Java8处理1000万个对象,我注意到streams可以将应用程序的速度降低25%。有趣的是,当我的集合也为空时,就会发生这种情况,所以这是流的恒定初始化时间。为了重现这个问题,考虑下面的代码: long start = System.nanoTime(); for (int i = 0; i < 10_000_000; i++) { Set<String> set = Collections.emptySe

我正在重写一个应用程序,它涉及到使用Java8处理1000万个对象,我注意到streams可以将应用程序的速度降低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);
使用
set.forEach(c)
结果将是7 vs 5 ms

当然,numer很小,我的基准测试也很原始,但是这个例子是否表明初始化流和闭包时会有开销


(实际上,由于代码> SET/CODE是空的,在这种情况下,闭包的初始化成本不应该是重要的,但是,如果我考虑在以前手工创建闭包),

< P>,您所看到的成本与“闭包”根本不相关,但与“代码>流初始化”的成本无关。 让我们以您的三个示例代码为例:

for (int i = 0; i < 10_000_000; i++) {
    Set<String> set = Collections.emptySet();
    set.stream().forEach(s -> System.out.println(s));
}
现在JIT又开始了:空位?好吧,这是一个没有行动,故事结束

set.forEach(System.out::println);
是否为集合创建了一个
迭代器
,该迭代器始终为空?同样的道理,JIT也起了作用

首先,代码的问题是您没有考虑JIT;对于真实的测量,在测量之前至少运行10k循环,因为JIT需要执行10k执行(至少热点是这样)


现在,lambdas:它们是呼叫站点,只链接一次;但是初始链接的成本仍然存在,当然,在循环中,您包括了这个成本。在进行测量之前,试着只运行一个循环,这样就不会影响成本

总之,这不是一个有效的微基准。使用卡尺或jmh来真正测量性能

一个很好的视频来了解lambdas是如何工作的。现在它有点旧了,JVM比现在使用lambdas时要好得多


如果您想了解更多信息,请查阅有关
invokedynamic

的文献,我也遇到过这个问题,让我非常沮丧和失望。制作一百万个条目,用常规for循环和用java 8的findfirst查找第一个条目,并尝试计时。如果使用旧方法,速度会快得多,特别是在if语句中添加break关键字clojure!=关闭,
消费者
不是关闭。在讨论Lambdas与Anon类的比较后,我们发现肯定有一些启动开销。但在许多情况下,一旦支付了启动开销,流的速度就会比相应的for循环快。因此,您应该使用实际数据进行一些测试。(而且,你的测量方法完全失败了,所以你得到的大多是无用的数字。)简短的回答是“是的,有一些”——但如果你真的想知道,试着更新你的应用程序,用真实的数据测量它的性能。以2013年JVM语言峰会上Sergey Kukcenko的演讲为例(通过严格的测量。)几乎任何源为集合的“表现良好”的流管道(例如,filter map reduce)都会优于相应的for循环,因为一旦有足够的数据克服启动差异,元素访问路径就会快得多;O(n)的系数与迭代器相比,streams的术语更少。最终这一点占主导地位。InvokedDynamic和JIT的参考文献不错。您还有其他关于优化noobs的好的JIT参考文献吗?谢谢!我应该阅读并观看视频,但在此之前有一个简短的问题:当您说:“当然,初始链接的成本仍然存在,在您的循环中,您包括了这个成本。”Java编译器不是在运行之前翻译lambda吗,或者在运行时发生了什么事情吗?这就是InvokedDynamic的目的吗?@Ali”Java编译器不是在运行之前翻译lambda吗,或者在运行时发生了什么事情吗?这就是InvokedDynamic的目的吗?"是的,这就是InvokedDynamic的作用。生成的调用站点具有所谓的引导方法,InvokedDynamic会询问引导方法应该链接什么代码。理论上,如果参数的类型或数量发生变化,引导方法可能会再次被查询,但在Java中,情况从来不是这样。在例如,在JVM上实现的动态语言,如Jython和JRuby。@Ali而且一旦代码链接起来,JIT就可以根据其核心内容对其进行优化,这是另一个伟大的特性;这就是为什么,例如,JRuby的性能比原生Ruby实现好——到目前为止!@EddieB可能感兴趣;至于它是如何工作的,interna嗯,我不知道;)这需要CS专家才能理解。但本视频以一种非常容易理解的方式解释了内联、锁粗化、循环展开、转义分析等。
for (int i = 0; i < 10_000_000; i++) {
    Set<String> set = Collections.emptySet();
    for (String s : set) {
        System.out.println(s);
    }
}
set.forEach(System.out::println);