Age*_*ntX 5 java iterator java-8 java-stream
所以我正在阅读一本关于Java 8的书,当我看到他们在外部和内部迭代之间进行比较并考虑比较两者时,性能明智.
我有一个方法,只是总结了一系列整数n.
迭代的:
private static long iterativeSum(long n) {
long startTime = System.nanoTime();
long sum = 0;
for(long i=1; i<=n; i++) {
sum+=i;
}
long endTime = System.nanoTime();
System.out.println("Iterative Sum Duration: " + (endTime-startTime)/1000000);
return sum;
}
Run Code Online (Sandbox Code Playgroud)
顺序一个 - 使用内部迭代
private static long sequentialSum(long n) {
long startTime = System.nanoTime();
//long sum = LongStream.rangeClosed(1L, n)
long sum = Stream.iterate(1L, i -> i+1)
.limit(n)
.reduce(0L, (i,j) -> i+j);
long endTime = System.nanoTime();
System.out.println("Sequential Sum Duration: " + (endTime-startTime)/1000000);
return sum;
}
Run Code Online (Sandbox Code Playgroud)
我尝试对它们进行一些基准测试,结果发现使用外部迭代的那个比使用内部迭代的那个好得多.
这是我的驱动程序代码:
public static void main(String[] args) {
long n = 100000000L;
for(int i=0;i<10000;i++){
iterativeSum(n);
sequentialSum(n);
}
iterativeSum(n);
sequentialSum(n);
}
Run Code Online (Sandbox Code Playgroud)
Iteravtive的运行时间总是<50ms,而Sequential的执行时间总是> 250ms.
我无法理解为什么内部迭代不会在这里执行外部迭代?
Tag*_*eev 12
尽管所呈现的结果完全不相关,但观察到的效果实际上发生了:Stream 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);
}
}
Run Code Online (Sandbox Code Playgroud)
这是基线测试:普通循环.我的方框上的结果如下:
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
Run Code Online (Sandbox Code Playgroud)
这是您基于Stream 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);
}
Run Code Online (Sandbox Code Playgroud)
结果如下所示:
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
Run Code Online (Sandbox Code Playgroud)
非常令人失望:慢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);
}
Run Code Online (Sandbox Code Playgroud)
结果如下:
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
Run Code Online (Sandbox Code Playgroud)
现在好多了,但仍然慢了2.8-10倍.另一种方法是使用范围:
public static long rangeSum(long n) {
return LongStream.rangeClosed(1, n).sum();
}
@Benchmark
public long rs() {
return rangeSum(n);
}
Run Code Online (Sandbox Code Playgroud)
结果如下:
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
Run Code Online (Sandbox Code Playgroud)
现在它慢了3.1-4.5倍.这种缓慢的原因是Stream API具有非常长的调用链,该调用链达到MaxInlineLevelJVM限制,因此默认情况下无法完全内联.您可以增加此限制设置,-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
Run Code Online (Sandbox Code Playgroud)
好多了:现在它只慢了1.05-1.5倍.
这个测试的问题在于迭代版本的循环体非常简单,因此可以通过JIT编译器有效地展开和矢量化,并且对于复杂的Stream API代码,以相同的效率执行此操作要困难得多.但是在实际应用中,你不可能在循环中对连续数字求和(为什么不写n*(n+1)/2呢?).使用实际问题即使使用默认MaxInlineLevel设置,Stream API开销也会低得多.
| 归档时间: |
|
| 查看次数: |
348 次 |
| 最近记录: |