Java 8流无法预测的性能下降没有明显原因

Java 8流无法预测的性能下降没有明显原因,第1张

Java 8流无法预测的性能下降没有明显原因

此影响是由类型配置文件污染引起的。让我解释一个简化的基准

@State(Scope.Benchmark)public class Streams {    @Param({"500", "520"})    int iterations;    @Setup    public void init() {        for (int i = 0; i < iterations; i++) { Stream.empty().reduce((x, y) -> x);        }    }    @Benchmark    public long loop() {        return Stream.empty().count();    }}

尽管

iteration
此处的参数变化很小并且不会影响主基准循环,但结果却暴露出令人惊讶的2.5倍性能下降:

Benchmark     (iterations)   Mode  Cnt      Score     Error   UnitsStreams.loop500  thrpt    5  29491,039 ± 240,953  ops/msStreams.loop520  thrpt    5  11867,860 ± 344,779  ops/ms

现在让我们运行带有

-prof perfasm
选项的JMH 来查看最热门的代码区域:

快速案例(迭代次数= 500):

....[Hottest Methods (after inlining)].................................. 48,66%  bench.generated.Streams_loop::loop_thrpt_jmhStub 23,14%  <unknown>  2,99%  java.util.stream.Sink$ChainedReference::<init>  1,98%  org.openjdk.jmh.infra.Blackhole::consume  1,68%  java.util.Objects::requireNonNull  0,65%  java.util.stream.AbstractPipeline::evaluate

慢速情况(迭代次数= 520):

....[Hottest Methods (after inlining)].................................. 40,09%  java.util.stream.ReduceOps$ReduceOp::evaluateSequential 22,02%  <unknown> 17,61%  bench.generated.Streams_loop::loop_thrpt_jmhStub  1,25%  org.openjdk.jmh.infra.Blackhole::consume  0,74%  java.util.stream.AbstractPipeline::evaluate

看起来情况很慢

ReduceOp.evaluateSequential
,在非内联方法中花费的时间最多。此外,如果我们研究此方法的汇编代码,则会发现最长的 *** 作是
checkcast

您知道HotSpot编译器的工作原理:在JIT启动之前,将在解释器中执行一个方法一段时间,以收集配置文件数据,例如,调用了哪些方法,看到了哪些类,采用了哪些分支等。也以C1编译的代码收集。该配置文件然后用于生成C2优化的代码。但是,如果应用程序在中间更改执行模式,则生成的代码对于修改后的行为可能不是最佳的。

让我们使用

-XX:+PrintMethodData
(在调试JVM中可用)比较执行配置文件:

----- Fast case -----java.util.stream.ReduceOps$ReduceOp::evaluateSequential(Ljava/util/stream/PipelineHelper;Ljava/util/Spliterator;)Ljava/lang/Object;  interpreter_invocation_count:    13382   invocation_counter:   13382   backedge_counter:         0   mdo size: 552 bytes0 aload_11 fast_aload_02 invokevirtual 3 <java/util/stream/ReduceOps$ReduceOp.makeSink()Ljava/util/stream/ReduceOps$AccumulatingSink;>   0   bci: 2    VirtualCallData     count(0) entries(1)   'java/util/stream/ReduceOps'(12870 1.00)5 aload_26 invokevirtual 4 <java/util/stream/PipelineHelper.wrapAndCopyInto(Ljava/util/stream/Sink;Ljava/util/Spliterator;)Ljava/util/stream/Sink;>   48  bci: 6    VirtualCallData     count(0) entries(1)   'java/util/stream/ReferencePipeline'(12870 1.00)9 checkcast 5 <java/util/stream/ReduceOps$AccumulatingSink>  96  bci: 9    ReceiverTypeData    count(0) entries(1)   'java/util/stream/ReduceOpsReducingSink'(12870 1.00)12 invokeinterface 6 <java/util/stream/ReduceOps$AccumulatingSink.get()Ljava/lang/Object;>   144 bci: 12   VirtualCallData     count(0) entries(1)   'java/util/stream/ReduceOpsReducingSink'(12870 1.00)17 areturn----- Slow case -----java.util.stream.ReduceOps$ReduceOp::evaluateSequential(Ljava/util/stream/PipelineHelper;Ljava/util/Spliterator;)Ljava/lang/Object;  interpreter_invocation_count:    54751   invocation_counter:   54751   backedge_counter:         0   mdo size: 552 bytes0 aload_11 fast_aload_02 invokevirtual 3 <java/util/stream/ReduceOps$ReduceOp.makeSink()Ljava/util/stream/ReduceOps$AccumulatingSink;>   0   bci: 2    VirtualCallData     count(0) entries(2)   'java/util/stream/ReduceOps'(16 0.00)   'java/util/stream/ReduceOps'(54223 1.00)5 aload_26 invokevirtual 4 <java/util/stream/PipelineHelper.wrapAndCopyInto(Ljava/util/stream/Sink;Ljava/util/Spliterator;)Ljava/util/stream/Sink;>   48  bci: 6    VirtualCallData     count(0) entries(2)   'java/util/stream/ReferencePipeline$Head'(16 0.00)   'java/util/stream/ReferencePipeline'(54223 1.00)9 checkcast 5 <java/util/stream/ReduceOps$AccumulatingSink>  96  bci: 9    ReceiverTypeData    count(0) entries(2)   'java/util/stream/ReduceOpsReducingSink'(16 0.00)   'java/util/stream/ReduceOpsReducingSink'(54228 1.00)12 invokeinterface 6 <java/util/stream/ReduceOps$AccumulatingSink.get()Ljava/lang/Object;>   144 bci: 12   VirtualCallData     count(0) entries(2)   'java/util/stream/ReduceOpsReducingSink'(16 0.00)   'java/util/stream/ReduceOpsReducingSink'(54228 1.00)17 areturn

您会看到,初始化循环耗时太长,以致其统计信息无法显示在执行配置文件中:所有虚拟方法都有两个实现,而checkcast也有两个不同的条目。在最快的情况下,概要文件不会受到污染:所有站点都是单态的,JIT可以轻松地内联和优化它们。

原始基准测试也是如此:

init()
方法中的较长流 *** 作污染了配置文件。如果您使用概要文件和分层编译选项,则结果可能会大不相同。例如,尝试

  1. -XX:-ProfileInterpreter
  2. -XX:Tier3InvocationThreshold=1000
  3. -XX:-TieredCompilation

最后,这个问题不是唯一的。由于配置文件污染,已经存在与性能降低相关的多个JVM错误:JDK-8015416,JDK-8015417,JDK-8059879
…希望在Java 9中会有所改善。



欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5601197.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-15
下一篇 2022-12-15

发表评论

登录后才能评论

评论列表(0条)

保存