栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 面试经验 > 面试问答

为什么lambda IntStream.anyMatch()比朴素的实现慢10个?

面试问答 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

为什么lambda IntStream.anyMatch()比朴素的实现慢10个?

您的基准测试实际上并不衡量

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$0(Contains.java:24)    at java.util.stream.MatchOps$2MatchSink.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个级别,但是在这里我们看到了更深的调用堆栈。如果我们用

-XX:MaxInlineLevel=20
分数来超越极限,将会变得更好:

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
数组上的迭代是一个简单的计数循环。JVM可以在此处应用广泛的循环优化:循环剥离,循环展开等。这不适用于用于遍历IntStream
while
-kind循环
forEachWithCancel
方法。循环优化的效果可以用以下方法测量
-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)

结论

一些开销,构建和遍历流,但这是完全了解,不能认为是一个错误。我不会说开销很大(即使50 ns /
op也不算多);但是,在此特定示例中,由于工作量极小,因此开销占主导。



转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/574072.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号