Java8 Lambda性能与公共功能

时间:2014-10-08 08:45:53

标签: java performance junit lambda

我使用Java8 VS进行了一些lambda性能测试。 Java8公共函数。

案例如下:

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

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

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

      

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

         

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

         

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

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

    所以,现在我非常想知道我是否在这里错过了什么。

    可以在此处找到来源:pastebin.com/BJBk4Tu6


    跟进:

    1. 将列表扩展为1.000.000项
    2. 并过滤所有年轻的成年女性
    3. 结果将是:

        

      使用ForEach的Lambda:59 ms

           

      公共职能:15毫秒

           

      带收集的Lambda:12毫秒

      然而,当我尝试过滤100,000次现有的1000,000人时,结果将是:

        

      使用ForEach的Lambda:227毫秒

           

      公共职能:134毫秒

           

      带收集的Lambda:172毫秒

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

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

      最新代码:pastebin.com/LcVhgnYv

1 个答案:

答案 0 :(得分:9)

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

部分引用another (otherwise unrelated) answer

  

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

     

对于非常基本的手动Java Microbenchmark的最简单形式,您必须考虑以下事项:

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

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

     

相关阅读:

     

我将这些基本步骤应用到您的程序中。这是一个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<Person> people;

    public Main() {

        for (int size=10; size<=1000000; size*=10) {

            Random r = new Random(0);
            people = new ArrayList<Person>();
            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<Person> lambdaOutput = new ArrayList<Person>();
        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<Person> lambdaOutput = new ArrayList<Person>();
        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<Person> defaultOutput = new ArrayList<Person>();
        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<Person> getFemaleYoungAdultsUsingLambdaUsingForEach() {
        List<Person> people = new ArrayList<Person>();
        this.people.stream().filter(
                (p) -> p.getSex() == Person.FEMALE &&
                p.getAge() >= 18 &&
                p.getAge() <= 25).forEach(people::add);
        return people;
    }

    public List<Person> getFemaleYoungAdultsUsingLambdaUsingCollect() {
        return this.people.stream().filter(
                (p) -> p.getSex() == Person.FEMALE &&
                p.getAge() >= 18 &&
                p.getAge() <= 25).collect(Collectors.toList());
    }

    public List<Person> getFemaleYoungAdultsUsingFunctions() {
        List<Person> people = new ArrayList<Person>();
        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,并对此发表一些进一步的见解。