Lambda性能提升,Java 8 vs 11

问题描述 投票:6回答:1

我在lambda vs方法引用上运行了一些JMH测试,看起来类似于:

IntStream......reduce(Integer::max)
vs.
IntSream.......reduce((i1, i2) -> Integer.max(i1, i2))

我注意到,在Java 8中,方法引用的执行速度是lambda的5倍。当我在Java 11中运行测试时,两种方法的执行时间与Java中的方法引用一样快。 8.因此,Java 11中lambda和方法引用之间的性能没有重大差异。

我的问题是:Java 8到11有哪些改进来提升这种性能?我正在使用OpenJDK。

编辑我的基准:

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Benchmark)
@Fork(value = 1, jvmArgs = {"-XX:CompileThreshold=5000"})
@Warmup(iterations = 2)
public class FindMaxInt {

@Param({"10000", "1000000", "10000000"})
private int n;

private List<Integer> data;

@Setup
public void setup(){
    data = createData();
}

@Benchmark
public void streamWithMethodReference(final Blackhole blackhole){
    int max = data.stream().mapToInt(Integer::intValue).reduce(Integer.MIN_VALUE, Integer::max);
    blackhole.consume(max);
}

@Benchmark
public void streamWithLambda(final Blackhole blackhole){
    int max = data.stream().mapToInt(Integer::intValue).reduce(Integer.MIN_VALUE, (i1, i2) -> Integer.max(i1, i2));
    blackhole.consume(max);
}
java performance lambda jvm java-stream
1个回答
15
投票

以下是thisthis答案中描述的效果组合。

不同的内联树解释了不同的结果。与方法引用相比,Lambda具有一个更多级别的间接,因此在JIT编译期间,具有lambda的表达式可能更早地达到内联深度限制。默认值为-XX:MaxInlineLevel=9

使用-XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining运行基准测试以查看整个内联树。这是我们在JDK 8上得到的:

1563  560       4       bench.FindMaxInt::streamWithLambda (38 bytes)
                           @ 3   java.util.stream.IntPipeline::<init> (7 bytes)   inline (hot)
                             @ 3   java.util.stream.AbstractPipeline::<init> (91 bytes)   inline (hot)
                               @ 1   java.util.stream.PipelineHelper::<init> (5 bytes)   inline (hot)
                                 @ 1   java.lang.Object::<init> (1 bytes)   inline (hot)
                               @ 51   java.util.stream.StreamOpFlag::combineOpFlags (9 bytes)   inline (hot)
                                 @ 2   java.util.stream.StreamOpFlag::getMask (30 bytes)   inline (hot)
                               @ 66   java.util.stream.IntPipeline$StatelessOp::opIsStateful (2 bytes)   inline (hot)
                           @ 4   java.util.Collection::stream (11 bytes)   inline (hot)
                            \-> TypeProfile (5120/5120 counts) = java/util/ArrayList
                             @ 1   java.util.ArrayList::spliterator (12 bytes)   inline (hot)
                               @ 8   java.util.ArrayList$ArrayListSpliterator::<init> (26 bytes)   inline (hot)
                                 @ 1   java.lang.Object::<init> (1 bytes)   inline (hot)
                             @ 7   java.util.stream.StreamSupport::stream (19 bytes)   inline (hot)
                               @ 1   java.util.Objects::requireNonNull (14 bytes)   inline (hot)
                               @ 11   java.util.stream.StreamOpFlag::fromCharacteristics (37 bytes)   inline (hot)
                                 @ 1   java.util.ArrayList$ArrayListSpliterator::characteristics (4 bytes)   inline (hot)
                                  \-> TypeProfile (5124/5124 counts) = java/util/ArrayList$ArrayListSpliterator
                               @ 15   java.util.stream.ReferencePipeline$Head::<init> (8 bytes)   inline (hot)
                                 @ 4   java.util.stream.ReferencePipeline::<init> (8 bytes)   inline (hot)
                                   @ 4   java.util.stream.AbstractPipeline::<init> (55 bytes)   inline (hot)
                                     @ 1   java.util.stream.PipelineHelper::<init> (5 bytes)   inline (hot)
                                       @ 1   java.lang.Object::<init> (1 bytes)   inline (hot)
                           @ 9   java.lang.invoke.LambdaForm$MH/883049899::linkToTargetMethod (8 bytes)   force inline by annotation
                             @ 4   java.lang.invoke.LambdaForm$MH/1922154895::identity_L (8 bytes)   force inline by annotation
                           @ 14   java.util.stream.ReferencePipeline::mapToInt (26 bytes)   inline (hot)
                            \-> TypeProfile (5120/5120 counts) = java/util/stream/ReferencePipeline$Head
                             @ 1   java.util.Objects::requireNonNull (14 bytes)   inline (hot)
                             @ 22   java.util.stream.ReferencePipeline$4::<init> (20 bytes)   inline (hot)
                               @ 16   java.util.stream.IntPipeline$StatelessOp::<init> (29 bytes)   inline (hot)
                                 @ 3   java.util.stream.IntPipeline::<init> (7 bytes)   inline (hot)
                                   @ 3   java.util.stream.AbstractPipeline::<init> (91 bytes)   inline (hot)
                                     @ 1   java.util.stream.PipelineHelper::<init> (5 bytes)   inline (hot)
                                       @ 1   java.lang.Object::<init> (1 bytes)   inline (hot)
                                     @ 51   java.util.stream.StreamOpFlag::combineOpFlags (9 bytes)   inline (hot)
                                       @ 2   java.util.stream.StreamOpFlag::getMask (30 bytes)   inline (hot)
                                     @ 66   java.util.stream.IntPipeline$StatelessOp::opIsStateful (2 bytes)   inline (hot)
                           @ 21   java.lang.invoke.LambdaForm$MH/883049899::linkToTargetMethod (8 bytes)   force inline by annotation
                             @ 4   java.lang.invoke.LambdaForm$MH/1922154895::identity_L (8 bytes)   force inline by annotation
                           @ 26   java.util.stream.IntPipeline::reduce (16 bytes)   inline (hot)
                            \-> TypeProfile (5120/5120 counts) = java/util/stream/ReferencePipeline$4
                             @ 3   java.util.stream.ReduceOps::makeInt (18 bytes)   inline (hot)
                               @ 1   java.util.Objects::requireNonNull (14 bytes)   inline (hot)
                               @ 14   java.util.stream.ReduceOps$5::<init> (16 bytes)   inline (hot)
                                 @ 12   java.util.stream.ReduceOps$ReduceOp::<init> (10 bytes)   inline (hot)
                                   @ 1   java.lang.Object::<init> (1 bytes)   inline (hot)
                             @ 6   java.util.stream.AbstractPipeline::evaluate (94 bytes)   inline (hot)
                               @ 50   java.util.stream.AbstractPipeline::isParallel (8 bytes)   inline (hot)
                               @ 80   java.util.stream.TerminalOp::getOpFlags (2 bytes)   inline (hot)
                                \-> TypeProfile (5130/5130 counts) = java/util/stream/ReduceOps$5
                               @ 85   java.util.stream.AbstractPipeline::sourceSpliterator (265 bytes)   inline (hot)
                                 @ 79   java.util.stream.AbstractPipeline::isParallel (8 bytes)   inline (hot)
                               @ 88   java.util.stream.ReduceOps$ReduceOp::evaluateSequential (18 bytes)   inline (hot)
                                 @ 2   java.util.stream.ReduceOps$5::makeSink (5 bytes)   inline (hot)
                                   @ 1   java.util.stream.ReduceOps$5::makeSink (16 bytes)   inline (hot)
                                     @ 12   java.util.stream.ReduceOps$5ReducingSink::<init> (15 bytes)   inline (hot)
                                       @ 11   java.lang.Object::<init> (1 bytes)   inline (hot)
                                 @ 6   java.util.stream.AbstractPipeline::wrapAndCopyInto (18 bytes)   inline (hot)
                                   @ 3   java.util.Objects::requireNonNull (14 bytes)   inline (hot)
                                   @ 9   java.util.stream.AbstractPipeline::wrapSink (37 bytes)   inline (hot)
                                     @ 1   java.util.Objects::requireNonNull (14 bytes)   inline (hot)
                                     @ 23   java.util.stream.ReferencePipeline$4::opWrapSink (10 bytes)   inline (hot)
                                      \-> TypeProfile (5081/5081 counts) = java/util/stream/ReferencePipeline$4
                                       @ 6   java.util.stream.ReferencePipeline$4$1::<init> (11 bytes)   inline (hot)
                                         @ 7   java.util.stream.Sink$ChainedReference::<init> (16 bytes)   inline (hot)
                                           @ 1   java.lang.Object::<init> (1 bytes)   inline (hot)
                                           @ 6   java.util.Objects::requireNonNull (14 bytes)   inline (hot)
                                   @ 13   java.util.stream.AbstractPipeline::copyInto (53 bytes)   inline (hot)
                                     @ 1   java.util.Objects::requireNonNull (14 bytes)   inline (hot)
                                     @ 9   java.util.stream.AbstractPipeline::getStreamAndOpFlags (5 bytes)   accessor
                                     @ 12   java.util.stream.StreamOpFlag::isKnown (19 bytes)   inline (hot)
                                     @ 20   java.util.Spliterator::getExactSizeIfKnown (25 bytes)   inline (hot)
                                      \-> TypeProfile (5081/5081 counts) = java/util/ArrayList$ArrayListSpliterator
                                       @ 1   java.util.ArrayList$ArrayListSpliterator::characteristics (4 bytes)   inline (hot)
                                       @ 19   java.util.ArrayList$ArrayListSpliterator::estimateSize (11 bytes)   inline (hot)
                                         @ 1   java.util.ArrayList$ArrayListSpliterator::getFence (48 bytes)   inline (hot)
                                           @ 38   java.util.ArrayList::access$000 (5 bytes)   accessor
                                     @ 25   java.util.stream.Sink$ChainedReference::begin (11 bytes)   inline (hot)
                                      \-> TypeProfile (5081/5081 counts) = java/util/stream/ReferencePipeline$4$1
                                       @ 5   java.util.stream.ReduceOps$5ReducingSink::begin (9 bytes)   inline (hot)
                                        \-> TypeProfile (5079/5079 counts) = java/util/stream/ReduceOps$5ReducingSink
                                     @ 32   java.util.ArrayList$ArrayListSpliterator::forEachRemaining (129 bytes)   inline (hot)
                                       @ 51   java.util.ArrayList::access$000 (5 bytes)   accessor
                                       @ 99   java.util.stream.ReferencePipeline$4$1::accept (23 bytes)   inline (hot)
                                         @ 12   bench.FindMaxInt$$Lambda$8/390011259::applyAsInt (8 bytes)   inline (hot)
                                          \-> TypeProfile (13752/13752 counts) = bench/FindMaxInt$$Lambda$8
                                           @ 4   java.lang.Integer::intValue (5 bytes)   accessor
                                         @ 17   java.util.stream.ReduceOps$5ReducingSink::accept (19 bytes)   inline (hot)
                                          \-> TypeProfile (13752/13752 counts) = java/util/stream/ReduceOps$5ReducingSink
                                           @ 10   bench.FindMaxInt$$Lambda$9/208515840::applyAsInt (6 bytes)   inline (hot)
                                            \-> TypeProfile (9107/9107 counts) = bench/FindMaxInt$$Lambda$9
                                             @ 2   bench.FindMaxInt::lambda$streamWithLambda$0 (6 bytes)   inline (hot)
                                               @ 2   java.lang.Integer::max (6 bytes)   inlining too deep
                                     @ 38   java.util.stream.Sink$ChainedReference::end (10 bytes)   inline (hot)
                                       @ 4   java.util.stream.Sink::end (1 bytes)   inline (hot)
                                        \-> TypeProfile (5125/5125 counts) = java/util/stream/ReduceOps$5ReducingSink
                                 @ 12   java.util.stream.ReduceOps$5ReducingSink::get (5 bytes)   inline (hot)
                                   @ 1   java.util.stream.ReduceOps$5ReducingSink::get (8 bytes)   inline (hot)
                                     @ 4   java.lang.Integer::valueOf (32 bytes)   inline (hot)
                                       @ 28   java.lang.Integer::<init> (10 bytes)   inline (hot)
                                         @ 1   java.lang.Number::<init> (5 bytes)   inline (hot)
                                           @ 1   java.lang.Object::<init> (1 bytes)   inline (hot)
                             @ 12   java.lang.Integer::intValue (5 bytes)   accessor
                           @ 34   org.openjdk.jmh.infra.Blackhole::consume (28 bytes)   disallowed by CompilerOracle

关键线如下。它们意味着在最终调用Integer.max时内联中断,因为达到了9个级别的默认限制。

@ 2   bench.FindMaxInt::lambda$streamWithLambda$0 (6 bytes)   inline (hot)
  @ 2   java.lang.Integer::max (6 bytes)   inlining too deep

在JDK 11上,内联树的形状非常不同:

1588  705       4       bench.FindMaxInt::streamWithLambda (38 bytes)
                           @ 4   java.util.Collection::stream (11 bytes)   inline (hot)
                            \-> TypeProfile (5263/5263 counts) = java/util/ArrayList
                             @ 1   java.util.ArrayList::spliterator (12 bytes)   inline (hot)
                               @ 8   java.util.ArrayList$ArrayListSpliterator::<init> (26 bytes)   inline (hot)
                                 @ 6   java.lang.Object::<init> (1 bytes)   inline (hot)
                             @ 7   java.util.stream.StreamSupport::stream (19 bytes)   inline (hot)
                               @ 1   java.util.Objects::requireNonNull (14 bytes)   inline (hot)
                               @ 11   java.util.stream.StreamOpFlag::fromCharacteristics (37 bytes)   inline (hot)
                                 @ 1   java.util.ArrayList$ArrayListSpliterator::characteristics (4 bytes)   inline (hot)
                                  \-> TypeProfile (5125/5125 counts) = java/util/ArrayList$ArrayListSpliterator
                               @ 15   java.util.stream.ReferencePipeline$Head::<init> (8 bytes)   inline (hot)
                                 @ 4   java.util.stream.ReferencePipeline::<init> (8 bytes)   inline (hot)
                                   @ 4   java.util.stream.AbstractPipeline::<init> (55 bytes)   inline (hot)
                                     @ 1   java.util.stream.PipelineHelper::<init> (5 bytes)   inline (hot)
                                       @ 1   java.lang.Object::<init> (1 bytes)   inline (hot)
                           @ 9   java.lang.invoke.Invokers$Holder::linkToTargetMethod (8 bytes)   force inline by annotation
                             @ 4   java.lang.invoke.LambdaForm$MH/0x0000000800060440::invoke (8 bytes)   force inline by annotation
                           @ 14   java.util.stream.ReferencePipeline::mapToInt (26 bytes)   inline (hot)
                            \-> TypeProfile (5263/5263 counts) = java/util/stream/ReferencePipeline$Head
                             @ 1   java.util.Objects::requireNonNull (14 bytes)   inline (hot)
                             @ 22   java.util.stream.ReferencePipeline$4::<init> (20 bytes)   inline (hot)
                               @ 16   java.util.stream.IntPipeline$StatelessOp::<init> (29 bytes)   inline (hot)
                                 @ 3   java.util.stream.IntPipeline::<init> (7 bytes)   inline (hot)
                                   @ 3   java.util.stream.AbstractPipeline::<init> (91 bytes)   inline (hot)
                                     @ 1   java.util.stream.PipelineHelper::<init> (5 bytes)   inline (hot)
                                       @ 1   java.lang.Object::<init> (1 bytes)   inline (hot)
                                     @ 51   java.util.stream.StreamOpFlag::combineOpFlags (9 bytes)   inline (hot)
                                       @ 2   java.util.stream.StreamOpFlag::getMask (30 bytes)   inline (hot)
                                     @ 66   java.util.stream.IntPipeline$StatelessOp::opIsStateful (2 bytes)   inline (hot)
                           @ 21   java.lang.invoke.Invokers$Holder::linkToTargetMethod (8 bytes)   force inline by annotation
                             @ 4   java.lang.invoke.LambdaForm$MH/0x0000000800060440::invoke (8 bytes)   force inline by annotation
                           @ 26   java.util.stream.IntPipeline::reduce (16 bytes)   inline (hot)
                            \-> TypeProfile (5263/5263 counts) = java/util/stream/ReferencePipeline$4
                             @ 3   java.util.stream.ReduceOps::makeInt (18 bytes)   inline (hot)
                               @ 1   java.util.Objects::requireNonNull (14 bytes)   inline (hot)
                               @ 14   java.util.stream.ReduceOps$6::<init> (16 bytes)   inline (hot)
                                 @ 12   java.util.stream.ReduceOps$ReduceOp::<init> (10 bytes)   inline (hot)
                                   @ 1   java.lang.Object::<init> (1 bytes)   inline (hot)
                             @ 6   java.util.stream.AbstractPipeline::evaluate (94 bytes)   inline (hot)
                               @ 50   java.util.stream.AbstractPipeline::isParallel (8 bytes)   inline (hot)
                               @ 80   java.util.stream.TerminalOp::getOpFlags (2 bytes)   inline (hot)
                                \-> TypeProfile (5362/5362 counts) = java/util/stream/ReduceOps$6
                               @ 85   java.util.stream.AbstractPipeline::sourceSpliterator (265 bytes)   inline (hot)
                                 @ 79   java.util.stream.AbstractPipeline::isParallel (8 bytes)   inline (hot)
                               @ 88   java.util.stream.ReduceOps$ReduceOp::evaluateSequential (18 bytes)   already compiled into a big method
                             @ 12   java.lang.Integer::intValue (5 bytes)   accessor
                           @ 34   org.openjdk.jmh.infra.Blackhole::consume (28 bytes)   disallowed by CompileCommand

由于不同的原因,编译树切断得更早:

@ 88   java.util.stream.ReduceOps$ReduceOp::evaluateSequential (18 bytes)   already compiled into a big method

JDK 11中的默认垃圾收集器已更改为G1。由于G1障碍,编译后的代码看起来更大,这就是内联启发式阻止最热的forEachRemaining循环内联到streamWithLambda方法的原因。

实际上,这不是JDK 11中的优化,而是更像是另一种方式。然而,这个特定基准的整体表现似乎更好,因为内联树截止发生在最热的循环之外。

Inlining tree

© www.soinside.com 2019 - 2024. All rights reserved.