具有相同运行时类但静态类型不同的对象的不同性能

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

考虑以下jmh基准

@State(Scope.Benchmark)
@BenchmarkMode(Array(Mode.Throughput))
class So59893913 {
  def seq(xs: Seq[Int]) = xs.sum
  def range(xs: Range) = xs.sum

  val xs = 1 until 100000000
  @Benchmark def _seq = seq(xs)
  @Benchmark def _range = range(xs)
}

给出xs引用作为参数传递给Range.Inclusiveseq方法的运行时类range的相同对象,因此,尽管方法的声明静态类型不同,但动态分派应调用sum的相同实现。参数,为什么性能似乎如此显着地变化,如下所示?

sbt "jmh:run -i 10 -wi 5 -f 2 -t 1 -prof gc bench.So59893913"

[info] Benchmark                                          Mode  Cnt          Score          Error   Units
[info] So59893913._range                                 thrpt   20  334923591.408 ± 22126865.963   ops/s
[info] So59893913._range:·gc.alloc.rate                  thrpt   20         ≈ 10⁻⁴                 MB/sec
[info] So59893913._range:·gc.alloc.rate.norm             thrpt   20         ≈ 10⁻⁷                   B/op
[info] So59893913._range:·gc.count                       thrpt   20            ≈ 0                 counts
[info] So59893913._seq                                   thrpt   20  193509091.399 ±  2347303.746   ops/s
[info] So59893913._seq:·gc.alloc.rate                    thrpt   20       2811.311 ±       34.142  MB/sec
[info] So59893913._seq:·gc.alloc.rate.norm               thrpt   20         16.000 ±        0.001    B/op
[info] So59893913._seq:·gc.churn.PS_Eden_Space           thrpt   20       2811.954 ±       33.656  MB/sec
[info] So59893913._seq:·gc.churn.PS_Eden_Space.norm      thrpt   20         16.004 ±        0.035    B/op
[info] So59893913._seq:·gc.churn.PS_Survivor_Space       thrpt   20          0.013 ±        0.005  MB/sec
[info] So59893913._seq:·gc.churn.PS_Survivor_Space.norm  thrpt   20         ≈ 10⁻⁴                   B/op
[info] So59893913._seq:·gc.count                         thrpt   20       3729.000                 counts
[info] So59893913._seq:·gc.time                          thrpt   20       1864.000                     ms

特别注意gc.alloc.rate指标的差异。

scala performance scala-collections
1个回答
0
投票

发生两件事。

首先是,当xs具有静态类型Range时,对sum的调用是单态方法调用,JVM可以轻松地内联该方法并对其进行进一步优化。当xs的静态类型为Seq时,它将变成一个大形方法调用,不会内联和完全优化。

第二个是被调用的方法是实际上不是相同的。编译器在sum:中生成two

Range方法
scala> :javap -p scala.collection.immutable.Range
Compiled from "Range.scala"
public abstract class scala.collection.immutable.Range extends scala.collection.immutable.AbstractSeq<java.lang.Object> implements scala.collection.immutable.IndexedSeq<java.lang.Object>, scala.collection.immutable.StrictOptimizedSeqOps<java.lang.Object, scala.collection.immutable.IndexedSeq, scala.collection.immutable.IndexedSeq<java.lang.Object>>, java.io.Serializable {
...
public final <B> int sum(scala.math.Numeric<B>);
...
public final java.lang.Object sum(scala.math.Numeric);
...
}

第一个包含您在源代码中看到的实际实现。如您所见,它返回一个未装箱的int。第二个是:

  public final java.lang.Object sum(scala.math.Numeric);
    Code:
       0: aload_0
       1: aload_1
       2: invokevirtual #898                // Method sum:(Lscala/math/Numeric;)I
       5: invokestatic  #893                // Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
       8: areturn

如您所见,这只是调用了另一种sum方法,并将int装到java.lang.Integer中。

因此,在您的方法seq中,编译器仅知道具有返回类型sumjava.lang.Object方法的存在并调用该方法。它可能不会内联,并且返回的java.lang.Integer必须重新装箱,以便seq可以返回int。在range中,编译器可以生成对“真实” sum方法的调用,而无需对结果进行装箱和拆箱。 JVM在内联和优化代码方面也可以做得更好。

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