Java8 Lambda性能与公共函数

我已经使用Java8 VS对lambda性能进行了一些演示测试。 Java8公共function。

案例如下:

  1. 我有一个10人(5男5女)的名单。

  2. 我想知道哪个女人的年龄在18到25岁之间

现在,当我执行这些步骤数百万次时,结果将是:

使用ForEach的Lambda:395毫秒(使用JUnit时为396毫秒)

公共函数采用:173毫秒(使用JUnit时为169毫秒)

使用Collect的Lambda:334毫秒(使用JUnit时为335毫秒)

现在我没想到lambda的执行时间比普通函数长两倍到六倍。

所以,现在我非常想知道我是否错过了这里的一些东西。

源代码可以在这里找到: pastebin.com/BJBk4Tu6


跟进:

  1. 将列表扩展为1.000.000项时
  2. 并过滤所有年轻的成年女性一次

结果将是:

使用ForEach的Lambda:59毫秒

公共职能采取:15毫秒

Lambda with Collect:12毫秒

但是,当我尝试过滤100,000次现有1.000.000人的同一列表时,结果将是:

使用ForEach的Lambda:227毫秒

公共职能采取:134毫秒

收集Lambda:172毫秒

因此,作为最终结论:Lambdas在过滤较大列表时更快,而公共函数(旧方法)在过滤较小列表时更快。

此外,在过滤任何列表多次时,公共function更快,无论出于何种目的,您都需要这样做。

最新代码: pastebin.com/LcVhgnYv

正如评论中指出的那样:你很难从这样一个简单而孤立的微基准测试中得出任何结论。

部分引用另一个(不相关的)答案 :

为了正确可靠地测量执行时间,存在几种选择。 除了像VisualVM这样的分析器之外,还有像JMH或Caliper这样的框架,但不可否认,使用它们可能需要付出一些努力。

对于最简单的基本手动Java Microbenchmarkforms,您必须考虑以下事项:

  • 多次运行算法,让JIT有机会参与其中
  • 交替运行算法,而不是一个接一个地运行
  • 随着输入大小的增加运行算法
  • 以某种方式保存并打印计算结果,以防止计算被优化掉
  • 考虑垃圾收集器(GC) 可能会导致时序失真

这些只是经验法则 ,可能仍有意想不到的结果(有关详细信息,请参阅上面的链接)。 但是通过这种策略,您通常可以获得关于性能的良好指示,并且至少可以看出算法之间是否存在显着差异。

相关阅读:

  • 如何在Java中编写正确的微基准测试?
  • Java理论与实践:有缺陷的微基准的剖析
  • HotSpot Internals

我将这些基本步骤应用到您的程序中。 这是一个MCVE :

注意:其余部分已更新,以响应问题的后续编辑)

 import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Random; import java.util.stream.Collectors; class Person { public static final int MALE = 0; public static final int FEMALE = 1; private final String name; private final int sex; private final int age; public Person(String name, int sex, int age) { this.name = name; this.sex = sex; this.age = age; } public int getSex() { return sex; } public int getAge() { return age; } } public class Main { public static void main(String[] args) { new Main(); } private List people; public Main() { for (int size=10; size<=1000000; size*=10) { Random r = new Random(0); people = new ArrayList(); for (int i = 0; i < size; i++) { int s = r.nextInt(2); int a = 25 + r.nextInt(20); people.add(new Person("p" + i, s, a)); } int min = 10000000 / size; int max = 10 * min; for (int n = min; n <= max; n += min) { lambdaMethodUsingForEach(n); lambdaMethodUsingCollect(n); defaultMethod(n); } } } public void lambdaMethodUsingForEach(int n) { List lambdaOutput = new ArrayList(); long lambdaStart = System.currentTimeMillis(); for (int i = 0; i < n; i++) { lambdaOutput.addAll(getFemaleYoungAdultsUsingLambdaUsingForEach()); } System.out.printf("List size: %10d, runs: %10d, result: %10d, ForEach took: " + (System.currentTimeMillis() - lambdaStart) + " ms\n", people.size(), n, lambdaOutput.size()); } public void lambdaMethodUsingCollect(int n) { List lambdaOutput = new ArrayList(); long lambdaStart = System.currentTimeMillis(); for (int i = 0; i < n; i++) { lambdaOutput.addAll(getFemaleYoungAdultsUsingLambdaUsingCollect()); } System.out.printf("List size: %10d, runs: %10d, result: %10d, collect took: " + (System.currentTimeMillis() - lambdaStart) + " ms\n", people.size(), n, lambdaOutput.size()); } public void defaultMethod(int n) { List defaultOutput = new ArrayList(); long defaultStart = System.currentTimeMillis(); for (int i = 0; i < n; i++) { defaultOutput.addAll(getFemaleYoungAdultsUsingFunctions()); } System.out.printf("List size: %10d, runs: %10d, result: %10d, default took: " + (System.currentTimeMillis() - defaultStart) + " ms\n", people.size(), n, defaultOutput.size()); } public List getFemaleYoungAdultsUsingLambdaUsingForEach() { List people = new ArrayList(); this.people.stream().filter( (p) -> p.getSex() == Person.FEMALE && p.getAge() >= 18 && p.getAge() <= 25).forEach(people::add); return people; } public List getFemaleYoungAdultsUsingLambdaUsingCollect() { return this.people.stream().filter( (p) -> p.getSex() == Person.FEMALE && p.getAge() >= 18 && p.getAge() <= 25).collect(Collectors.toList()); } public List getFemaleYoungAdultsUsingFunctions() { List people = new ArrayList(); for (Person p : this.people) { if (p.getSex() == Person.FEMALE && p.getAge() >= 18 && p.getAge() <= 25) { people.add(p); } } return people; } } 

MyMachine®上的输出与此类似:

  ... List size: 10, runs: 10000000, result: 10000000, ForEach took: 1482 ms List size: 10, runs: 10000000, result: 10000000, collect took: 2014 ms List size: 10, runs: 10000000, result: 10000000, default took: 1013 ms ... List size: 100, runs: 1000000, result: 3000000, ForEach took: 664 ms List size: 100, runs: 1000000, result: 3000000, collect took: 515 ms List size: 100, runs: 1000000, result: 3000000, default took: 441 ms ... List size: 1000, runs: 100000, result: 2300000, ForEach took: 778 ms List size: 1000, runs: 100000, result: 2300000, collect took: 721 ms List size: 1000, runs: 100000, result: 2300000, default took: 841 ms ... List size: 10000, runs: 10000, result: 2450000, ForEach took: 970 ms List size: 10000, runs: 10000, result: 2450000, collect took: 971 ms List size: 10000, runs: 10000, result: 2450000, default took: 1119 ms ... List size: 100000, runs: 1000, result: 2536000, ForEach took: 976 ms List size: 100000, runs: 1000, result: 2536000, collect took: 1057 ms List size: 100000, runs: 1000, result: 2536000, default took: 1109 ms ... List size: 1000000, runs: 100, result: 2488600, ForEach took: 1323 ms List size: 1000000, runs: 100, result: 2488600, collect took: 1305 ms List size: 1000000, runs: 100, result: 2488600, default took: 1422 ms 

您可以看到ForEachdefault (公共方法)方法之间的差异即使对于较小的列表也会消失。 对于较大的列表,基于lambda的方法甚至似乎有一点点优势。

再次强调这一点:这是一个非常简单的微基准测试,即使这并不一定能说明这些方法在实践中的表现。 但是,至少可以合理地假设ForEach和公共方法之间的差异不如初始测试所建议的那么大。 Nevertleless:对于任何在JMH或Caliper中运行此操作的人来说,给我+1,并对此发表一些进一步的见解。