Разная производительность объекта с одинаковым классом времени выполнения, но с разным типом stati c - PullRequest
4 голосов
/ 24 января 2020

Рассмотрим следующий тест 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.Inclusive, переданный в качестве аргумента в методы seq и range, следовательно, dynamici c диспетчер должен вызывать ту же реализацию sum, несмотря на различный объявленный параметр метода типа stati c, почему производительность, по-видимому, отличается так резко, как указано ниже?

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 метриках.

1 Ответ

5 голосов
/ 24 января 2020

Происходят две вещи.

Во-первых, когда xs имеет тип * 10039 * stati Range, тогда этот вызов sum является вызовом метода * monmorphi c (поскольку sum является окончательным в Range), и JVM может легко встроить этот метод и оптимизировать его. Когда xs имеет тип stati c типа Seq, он становится вызовом метода megamorphi c, который не будет встроенным и полностью оптимизированным.

Вторым является то, что вызываемые методы являются на самом деле не то же самое . Компилятор генерирует два sum метода в 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 компилятор знает только о существовании метода sum, который имеет тип возврата java.lang.Object, и вызывает его. Вероятно, он не становится встроенным, и возвращаемое java.lang.Integer необходимо снова распаковать, чтобы seq могло вернуть int. В range компилятор может генерировать вызов «реального» метода sum без необходимости ставить и распаковывать результаты. JVM также может выполнять работу по внедрению и оптимизации кода.

...