基准测试:Java 8 Lambda和流如何使您的代码慢5倍

与长期的实现相比,Java 8 lambda和流的性能如何?

Lambda表达式和流在Java 8中受到了热烈的欢迎。这些是迄今为止很激动人心的功能,很长一段时间以来,它们就已经应用到Java中了。 新的语言功能使我们可以在代码中采用更具功能性的样式,并且在其中玩耍也很有趣。 非常有趣,应该是非法的。 然后我们变得可疑 ,并决定对它们进行测试。

我们已经完成了一个简单的任务,即在ArrayList中找到最大值,并测试了长期的实现与Java 8中可用的新方法的对比。老实说,结果令人惊讶。

Java 8中的命令式与功能式编程

我们喜欢直截了当,所以让我们看一下结果。 对于此基准,我们创建了一个ArrayList,为其中填充了100,000个随机整数,并实现了7种不同的方式来遍历所有值以找到最大值。 这些实现分为两类:具有Java 8中引入的新语言功能的功能样式和具有长期Java方法的命令式样式。

这是每种方法花费的时间:

**记录的最大错误是parallelStream上的0.042,完整的结果输出可在该文章的底部找到

**记录的最大错误是parallelStream上的0.042,完整的结果输出可在该文章的底部找到

外卖

  1. 哎呀! 使用Java 8提供的任何新方法来实现解决方案,都会使性能下降5倍左右。 有时,使用带有迭代器的简单循环比将lambda和流混入混合要好。 即使这意味着编写更多代码行并跳过那种甜蜜的语法糖。
  2. 使用迭代器或for-each循环是遍历ArrayList的最有效方法。 比具有索引int的传统for循环好两倍。
  3. 在Java 8方法中,使用并行流被证明更有效。 但是要当心, 在某些情况下,它实际上可能会使您减速。
  4. Lambas取代了它们在流和parallelStream实现之间的位置。 这是令人惊讶的,因为它们的实现是基于流API的。
  5. [编辑]事情并非总是如此:尽管我们想展示在lambda和流中引入错误有多么容易,但我们收到了很多社区反馈,要求为基准代码添加更多优化并删除对它们的装箱/拆箱。整数。 包括优化在内的第二组结果可在本文的底部获得。

等一下,我们到底在这里测试了什么?

让我们快速浏览一下每种方法,从最快到最慢:

命令式

forMaxInteger() –使用简单的for循环和int索引遍历列表:

public int forMaxInteger() {
    int max = Integer.MIN_VALUE;
    for (int i = 0; i < size; i++) {
        max = Integer.max(max, integers.get(i));
    }
    return max;
}

iteratorMaxInteger() –使用迭代器遍历列表:

public int iteratorMaxInteger() {
    int max = Integer.MIN_VALUE;
    for (Iterator<Integer> it = integers.iterator(); it.hasNext(); ) {
        max = Integer.max(max, it.next());
    }
    return max;
}

forEachLoopMaxInteger() –丢失迭代器,并使用For-Each循环遍历列表(不要误解为Java 8 forEach):

public int forEachLoopMaxInteger() {
    int max = Integer.MIN_VALUE;
    for (Integer n : integers) {
        max = Integer.max(max, n);
    }
    return max;
}

功能风格

parallelStreamMaxInteger() –在并行模式下使用Java 8流浏览列表:

public int parallelStreamMaxInteger() {
    Optional<Integer> max = integers.parallelStream().reduce(Integer::max);
    return max.get();
}

lambdaMaxInteger() –将lambda表达式与流一起使用。 甜蜜的一线:

public int lambdaMaxInteger() {
    return integers.stream().reduce(Integer.MIN_VALUE, (a, b) -> Integer.max(a, b));
}

forEachLambdaMaxInteger() –这对于我们的用例来说有点混乱。 新的Java 8 forEach功能可能最令人讨厌的是它只能使用最终变量,因此我们为最终包装器类创建了一些变通方法,该类可以访问我们正在更新的最大值:

public int forEachLambdaMaxInteger() {
    final Wrapper wrapper = new Wrapper();
    wrapper.inner = Integer.MIN_VALUE;

    integers.forEach(i -> helper(i, wrapper));
    return wrapper.inner.intValue();
}

public static class Wrapper {
    public Integer inner;
}

private int helper(int i, Wrapper wrapper) {
    wrapper.inner = Math.max(i, wrapper.inner);
    return wrapper.inner;
}

顺便说一句,如果我们已经在谈论forEach,请查看这个StackOverflow答案,我们就其一些缺点提供了一些有趣的见解。

streamMaxInteger() –使用Java 8流浏览列表:

public int streamMaxInteger() {
    Optional<Integer> max = integers.stream().reduce(Integer::max);
    return max.get();
}

优化基准

遵循本文的反馈意见,我们创建了基准的另一个版本。 与原始代码的所有差异都可以在此处查看 。 结果如下:

翻拍

TL; DR:更改摘要

  1. 该列表不再易失。
  2. forMax2的新方法删除了字段访问。
  3. forEachLambda中的冗余帮助程序功能已修复。 现在,lambda也正在分配一个值。 可读性较差,但速度更快。
  4. 自动装箱消除了。 如果您在Eclipse中为项目打开自动装箱警告,则旧代码中有15条警告。
  5. 在reduce之前使用mapToInt修复流代码。

感谢Patrick ReinhartRichard WarburtonYan BonnelSergey KuksenkoJeff MaxwellHenrik Gustafsson以及所有在Twitter上发表评论的人!

基础

为了运行此基准测试,我们使用了Java Microbenchmarking Harness JMH。 如果您想了解更多有关如何在自己的项目中使用它的信息, 请查看这篇文章 ,我们将通过动手示例来了解它的一些主要功能。

基准配置包括JVM的2个分支,5个预热迭代和5个测量迭代。 这些测试是使用Java 8u66和JMH 1.11.2在c3.xlarge Amazon EC2实例(4个vCPU,7.5 Mem(GiB),2 x 40 GB SSD存储)上运行的。 完整的源代码可在GitHub上找到 ,您可以在此处查看原始结果输出。

话虽这么说,但有一点免责声明:基准往往非常危险,要正确地制定基准则非常困难。 尽管我们尝试以最准确的方式运行它,但始终建议您先花一点盐。

最后的想法

使用Java 8时,要做的第一件事是尝试使用lambda表达式和流。 但是要当心:感觉真的很好,很甜,所以您可能会上瘾! 我们已经看到,坚持使用迭代器和for-each循环的更传统的Java编程风格,明显优于Java 8提供的新实现。当然,情况并非总是如此,但是在这个非常常见的示例中,它表明可以大约差5倍。 如果它影响系统的核心部分或创建新的瓶颈,这会变得非常可怕。

翻译自: https://www.javacodegeeks.com/2015/11/benchmark-java-8-lambdas-streams-can-make-code-5-times-slower.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值