即使呈现的结果是完全不相干的,观察到的效果确实发生:数据流API做了哪些对这种简单的任务不能完全即使在热身消除在实际应用中的开销。让我们写一个JMH基准:
@Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Fork(3)
@State(Scope.Benchmark)
public class IterativeSum {
@Param({ "100", "10000", "1000000" })
private int n;
public static long iterativeSum(long n) {
long sum = 0;
for(long i=1; i<=n; i++) {
sum+=i;
}
return sum;
}
@Benchmark
public long is() {
return iterativeSum(n);
}
}
这里的基准测试:纯循环。在我的箱子,结果如下:
Benchmark (n) Mode Cnt Score Error Units
IterativeSum.is 100 avgt 30 0.074 ± 0.001 us/op
IterativeSum.is 10000 avgt 30 6.361 ± 0.009 us/op
IterativeSum.is 1000000 avgt 30 688.527 ± 0.910 us/op
这是你的基于流API迭代版本:
public static long sequentialSumBoxed(long n) {
return Stream.iterate(1L, i -> i+1).limit(n)
.reduce(0L, (i,j) -> i+j);
}
@Benchmark
public long ssb() {
return sequentialSumBoxed(n);
}
结果是这样的:
Benchmark (n) Mode Cnt Score Error Units
IterativeSum.ssb 100 avgt 30 1.253 ± 0.084 us/op
IterativeSum.ssb 10000 avgt 30 134.959 ± 0.421 us/op
IterativeSum.ssb 1000000 avgt 30 9119.422 ± 22.817 us/op
非常失望:13-慢21倍。这个版本里面有很多装箱操作,这就是为什么创建原始流专业化的原因。让我们来看看非盒装版本:
public static long sequentialSum(long n) {
return LongStream.iterate(1L, i -> i+1).limit(n)
.reduce(0L, (i,j) -> i+j);
}
@Benchmark
public long ss() {
return sequentialSum(n);
}
结果如下:
Benchmark (n) Mode Cnt Score Error Units
IterativeSum.ss 100 avgt 30 0.661 ± 0.001 us/op
IterativeSum.ss 10000 avgt 30 67.498 ± 5.732 us/op
IterativeSum.ss 1000000 avgt 30 1982.687 ± 38.501 us/op
现在
好多了,但仍然比较慢2.8-10x倍。另一种方法是使用范围:
public static long rangeSum(long n) {
return LongStream.rangeClosed(1, n).sum();
}
@Benchmark
public long rs() {
return rangeSum(n);
}
结果如下:
Benchmark (n) Mode Cnt Score Error Units
IterativeSum.rs 100 avgt 30 0.316 ± 0.001 us/op
IterativeSum.rs 10000 avgt 30 28.646 ± 0.065 us/op
IterativeSum.rs 1000000 avgt 30 2158.962 ± 514.780 us/op
现在是慢3.1-4.5x倍。这个缓慢的原因是流API具有打MaxInlineLevel
JVM限制很长的调用链,因此它不能被默认完全内联。您可能会增加此限制设置像-XX:MaxInlineLevel=20
并得到以下结果:
Benchmark (n) Mode Cnt Score Error Units
IterativeSum.rs 100 avgt 30 0.111 ± 0.001 us/op
IterativeSum.rs 10000 avgt 30 9.552 ± 0.017 us/op
IterativeSum.rs 1000000 avgt 30 729.935 ± 31.915 us/op
好多了:现在只要1.05-1.5x慢。
该测试的问题在于迭代版本的循环体非常简单,因此可以通过JIT编译器进行高效的展开和向量化处理,而且对于复杂的Stream API代码,使用相同的效率难度更大。然而在实际应用中,你不可能在循环中总结数字(为什么不写n*(n+1)/2
代替?)。存在实际问题即使使用默认值MaxInlineLevel
设置,Stream API开销也要低得多。
fyi http://stackoverflow.com/questions/504103/how-do-i-write-a-correct-micro-benchmark-in-java你的测试没有做你期望的测试 – Zavior
我试过了在循环中运行这些方法超过一千次以预热JIT并遍历所有代码路径。但是我没有看到2之间的差距缩小。迭代总是<50,顺序(内部迭代)总是> 250ms – AgentX