Different performance of object with same runtime class but different static type

怎甘沉沦 提交于 2020-01-31 18:01:06

问题


Consider the following jmh benchmark

@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)
}

Given xs references the same object of runtime class Range.Inclusive passed in as argument to seq and range methods, hence dynamic dispatch should invoke the same implementation of sum, despite differing declared static type of method parameter, why the performance seems to differ so drastically as indicated below?

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

Particularly notice the difference in gc.alloc.rate metrics.


回答1:


Two things are going on.

The first is that when xs has the static type Range then that call to sum is a monomorphic method call (because sum is final in Range) and the JVM can easily inline that method and optimize it further. When xs has the static type Seq then it becomes a megamorphic method call which won't get inlined and fully optimized.

The second is that the methods that get called are not actually the same. The compiler generates two sum methods in 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);
...
}

The first one contains the actual implementation that you see in the source code. And as you can see it returns an unboxed int. The second one is this:

  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

As you see this one just calls the other sum method and boxes the int into a java.lang.Integer.

So in your method seq the compiler only knows about the existence of the sum method that has return type java.lang.Object and calls that one. It probably doesn't get inlined and the java.lang.Integer that it returns has to be unboxed again so seq can return an int. In range the compiler can generate a call to the "real" sum method without having to box and unbox the results. The JVM can also do a better job at inlining and optimizing the code.



来源:https://stackoverflow.com/questions/59899811/different-performance-of-object-with-same-runtime-class-but-different-static-typ

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!