正如评论中指出的:您很难从这样一个单一、简单和孤立的微基准测试运行中得出任何结论。
部分引用自另一个(否则不相关的)答案:
为了正确、可靠地测量执行时间,存在多种选择。除了分析器之外,例如VisualVM,有像这样的框架JMH or Caliper,但不可否认,使用它们可能需要一些努力。
For the simplest对于一个非常基本的手动 Java Microbenchmark 形式,您必须考虑以下事项:
- 多次运行算法,让 JIT 有机会发挥作用
- 交替运行算法,而不是一个接一个地运行
- 随着输入大小的增加运行算法
- 以某种方式保存并打印计算结果,以防止计算被优化掉
- 考虑一下时间安排may被垃圾收集器 (GC) 扭曲
这些只是经验法则,并且仍然可能会有意想不到的结果(更多详细信息请参阅上面的链接)。但通过这种策略,您通常可以获得有关性能的良好指示,并且至少可以了解是否可能存在really算法之间存在显着差异。
相关阅读:
- 如何用 Java 编写正确的微基准测试?
- Java 理论与实践:有缺陷的微基准剖析
- 热点内部结构
我将这些基本步骤应用到您的程序中。这是一个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;
}
}
My Machine® 上的输出大致如下:
...
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
您可以看到两者之间的区别ForEach
和default
即使对于较小的列表,(公共方法)方法也正在消失。对于较大的列表,基于 lambda 的方法似乎甚至有一点优势。
再次强调一下:这是一个很简单微基准测试,即使这也不一定能说明这些方法在实践中的性能。然而,至少可以合理地假设ForEach
并且公共方法并不像您最初测试建议的那么大。 Nevertleless:对于任何在 JMH 或 Caliper 中运行此程序并发布有关此内容的进一步见解的人,我都会+1。