
您的基准测试实际上并不衡量
anyMatch性能,而是衡量流开销。与非常简单的 *** 作(如五元素数组查找)相比,此开销可能会很明显。
如果我们从相对数转为绝对数,增速放缓看起来不会那么可怕。让我们测量延迟而不是吞吐量,以获得更清晰的画面。我省略了
lambdaIntStream基准测试,因为它的工作方式与完全相同
lambdaArrayStream。
Benchmark Mode Cnt Score Error UnitsContains.lambdaArrayStream avgt 5 53,242 ± 2,034 ns/opContains.naive avgt 5 5,876 ± 0,404 ns/op
5.8 ns大约是2.4 GHz CPU的14个周期。工作量如此之小,以至于任何额外的周期都会很明显。那么流 *** 作的开销是多少?
对象分配现在,使用
-prof gc探查器重新运行基准测试。它将显示堆分配的数量:
Benchmark Mode Cnt Score Error UnitsContains.lambdaArrayStream:·gc.alloc.rate.norm avgt 5 152,000 ± 0,001 B/opContains.naive:·gc.alloc.rate.norm avgt 5 ≈ 10⁻⁵ B/op
lambdaArrayStream每次迭代分配152个字节,而
naive基准测试则不分配任何内容。当然,分配不是免费的:至少构造了5个对象来支持
anyMatch,每个对象都需要几纳秒的时间:
- 拉姆达
i -> i == val
- IntPipeline.Head
- Spliterators.IntArraySpliterator
- MatchOps.MatchOp
- MatchOps.MatchSink
java.util.stream实施有点复杂,因为它必须支持流源,中间 *** 作和终端 *** 作的所有组合。如果您查看
anyMatch基准测试中的调用堆栈,则会看到类似以下内容:
at bench.Contains.lambda$lambdaArrayStream-XX:MaxInlineLevel=20(Contains.java:24) at java.util.stream.MatchOpsMatchSink.accept(MatchOps.java:119) at java.util.Spliterators$IntArraySpliterator.tryAdvance(Spliterators.java:1041) at java.util.stream.IntPipeline.forEachWithCancel(IntPipeline.java:162) at java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:498) at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:485) at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471) at java.util.stream.MatchOps$MatchOp.evaluateSequential(MatchOps.java:230) at java.util.stream.MatchOps$MatchOp.evaluateSequential(MatchOps.java:196) at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) at java.util.stream.IntPipeline.anyMatch(IntPipeline.java:477) at bench.Contains.lambdaArrayStream(Contains.java:23)
并非所有这些方法调用都可以内联。此外,JVM将内联限制为9个级别,但是在这里我们看到了更深的调用堆栈。如果我们用
Benchmark Mode Cnt Score Error UnitsContains.lambdaArrayStream avgt 5 33,294 ± 0,367 ns/op (was 53,242)Contains.naive avgt 5 5,822 ± 0,207 ns/op分数来超越极限,将会变得更好:
循环优化for
while数组上的迭代是一个简单的计数循环。JVM可以在此处应用广泛的循环优化:循环剥离,循环展开等。这不适用于用于遍历IntStream
的
forEachWithCancel-kind循环
-XX:LoopUnrollLimit=0-XX:-UseLoopPredicate方法。循环优化的效果可以用以下方法测量
Benchmark Mode Cnt Score Error UnitsContains.lambdaArrayStream avgt 5 33,153 ± 0,559 ns/opContains.naive avgt 5 9,853 ± 0,150 ns/op (was 5,876):
是结论
有
op也不算多);但是,在此特定示例中,由于工作量极小,因此开销占主导。 一些开销,构建和遍历流,但这是完全了解,不能认为是一个错误。我不会说开销很大(即使50 ns /
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)