案例如下:
>我有一个10人(5男5女)的名单.
>我想知道哪个女人的年龄在18到25岁之间
现在,当我执行这些步骤数百万次时,结果将是:
Lambda with ForEach took: 395 ms (396 ms using JUnit)
Public functions took: 173 ms (169 ms using JUnit)
Lambda with Collect took: 334 ms (335 ms using JUnit)
现在我没想到lambda的执行时间比普通函数长两倍到六倍.
所以,现在我非常想知道我是否错过了这里的一些东西.
源代码可以在这里找到:pastebin.com/BJBk4Tu6
跟进:
>将列表扩展为1.000.000项时
>并过滤所有年轻的成年女性一次
结果将是:
Lambda with ForEach took: 59 ms
Public functions took: 15 ms
Lambda with Collect took: 12 ms
但是,当我尝试过滤100,000次现有的1.000.000人时,结果将是:
Lambda with ForEach took: 227 ms
Public functions took: 134 ms
Lambda with Collect took: 172 ms
因此,作为最终结论:Lambdas在过滤较大列表时更快,而公共函数(旧方法)在过滤较小列表时更快.
此外,在过滤任何列表时,公共功能更快,无论出于何种目的,您都需要这样做.
最新代码:pastebin.com/LcVhgnYv
解决方法
从another (otherwise unrelated) answer部分引用:
In order to properly and reliably measure execution times,there exist several options. Apart from a profiler,like 07001,there are frameworks like 07002 or 07003,but admittedly,using them may be some effort.
For the simplest form of a very basic,manual Java Microbenchmark you have to consider the following:
- Run the algorithms multiple times,to give the JIT a chance to kick in
- Run the algorithms alternatingly and not only one after the other
- Run the algorithms with increasing input size
- Somehow save and print the results of the computation,to prevent the computation from being optimized away
- Consider that timings may be distorted by the garbage collector (GC)
These are only rules of thumb,and there may still be unexpected results (refer to the links above for more details). But with this strategy,you usually obtain a good indication about the performance,and at least can see whether it’s likely that there really are significant differences between the algorithms.
Related reading:
- 07004
- 07005
- 07006
我将这些基本步骤应用到您的程序中.这是MCVE:
NOTE: The remaining part was updated in response to the follow-up edit of the question)
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,collect took: " + (System.currentTimeMillis() - lambdaStart) + " ms\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,default took: " + (System.currentTimeMillis() - defaultStart) + " ms\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,collect took: 2014 ms List size: 10,default took: 1013 ms ... List size: 100,runs: 1000000,result: 3000000,ForEach took: 664 ms List size: 100,collect took: 515 ms List size: 100,default took: 441 ms ... List size: 1000,runs: 100000,result: 2300000,ForEach took: 778 ms List size: 1000,collect took: 721 ms List size: 1000,default took: 841 ms ... List size: 10000,runs: 10000,result: 2450000,ForEach took: 970 ms List size: 10000,collect took: 971 ms List size: 10000,default took: 1119 ms ... List size: 100000,runs: 1000,result: 2536000,ForEach took: 976 ms List size: 100000,collect took: 1057 ms List size: 100000,default took: 1109 ms ... List size: 1000000,runs: 100,result: 2488600,ForEach took: 1323 ms List size: 1000000,collect took: 1305 ms List size: 1000000,default took: 1422 ms
您可以看到ForEach和默认(公共方法)方法之间的差异即使对于较小的列表也会消失.对于较大的列表,基于lambda的方法甚至似乎有一点点优势.
再次强调这一点:这是一个非常简单的微基准测试,即使这并不一定能说明这些方法在实践中的表现.但是,至少可以合理地假设ForEach和公共方法之间的差异不如初始测试所建议的那么大. Nevertleless:对于任何在JMH或Caliper中运行此操作的人来说,我都有1个,并对此发表了一些进一步的见解.