scala-dev icon indicating copy to clipboard operation
scala-dev copied to clipboard

Can we avoid invokeinterface for Analyzer.global within Typer?

Open retronym opened this issue 6 years ago • 0 comments

Consider this benchmark.

package scala.scratch

import java.util.concurrent.TimeUnit

import org.openjdk.jmh.annotations._
import org.openjdk.jmh.infra.Blackhole

@BenchmarkMode(Array(Mode.AverageTime))
@Fork(2)
@Threads(1)
@Warmup(iterations = 20)
@Measurement(iterations = 10)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Benchmark)
class CakeBenchmark {

  val g = new Global
  val t1: T[g.type] = new HasGlobal[g.type](g) with T[g.type] {
    val inner = new Inner {}
  }
  val t2: T[g.type] = new HasGlobal[g.type](g) with T[g.type] {
    val inner = new Inner {}
  }
  val t3: T[g.type] = new HasGlobal[g.type](g) with T[g.type] {
    val inner = new Inner {}
  }
  val u1 = new U {
    val global: g.type = g
    val inner = new Inner {}
  }
  val u2 = new U {
    val global: g.type = g
    val inner = new Inner {}
  }
  val u3 = new U {
    val global: g.type = g
    val inner = new Inner {}
  }

  val v1: V[g.type] = new HasGlobal[g.type](g) with V[g.type] {
    val inner = new Inner {}
  }
  val v2: V[g.type] = new HasGlobal[g.type](g) with V[g.type] {
    val inner = new Inner {}
  }
  val v3: V[g.type] = new HasGlobal[g.type](g) with V[g.type] {
    val inner = new Inner {}
  }

  @Benchmark
  def testT(bh: Blackhole) = {
    bh.consume(t1.inner.gg)
    bh.consume(t2.inner.gg)
    bh.consume(t3.inner.gg)
  }
  @Benchmark
  def testU(bh: Blackhole) = {
    bh.consume(u1.inner.gg)
    bh.consume(u2.inner.gg)
    bh.consume(u3.inner.gg)
  }

  @Benchmark
  def testV(bh: Blackhole) = {
    bh.consume(v1.inner.gg)
    bh.consume(v2.inner.gg)
    bh.consume(v3.inner.gg)
  }

}


class Global {
  var i = 0
}

class HasGlobal[G <: Global](final val global: G)

trait U {
  val global: Global
  class Inner {
    final def gg = global
  }
  def inner: Inner
}

trait T[G <: Global with Singleton] {
  self: HasGlobal[G] =>

  class Inner {
    final def gg = global
  }
  def inner: Inner
}

trait V[G <: Global with Singleton] {
  self: HasGlobal[G] =>

  class Inner {
    private[this] val global = self.global
    final def gg = this.global
  }
  def inner: Inner
}

testU represents the status quo, after getting the the Typer.outer, we have to make an interface call to global.

  // access flags 0x11
  public final gg()Lscala/scratch/Global;
    ALOAD 0
    INVOKEVIRTUAL scala/scratch/U$Inner.scala$scratch$U$Inner$$$outer ()Lscala/scratch/U;
    INVOKEINTERFACE scala/scratch/U.global ()Lscala/scratch/Global; (itf)
    ARETURN
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x1001
  public synthetic scala$scratch$U$Inner$$$outer()Lscala/scratch/U;
    ALOAD 0
    GETFIELD scala/scratch/U$Inner.$outer : Lscala/scratch/U;
    ARETURN
    MAXSTACK = 1
    MAXLOCALS = 1

testT represents an alternative in which global was moved into an invokevirtual, which is monorphic because HasGlobal.global is final.

  // access flags 0x11
  // signature ()TG;
  // declaration: G ()
  public final gg()Lscala/scratch/Global;
    ALOAD 0
    INVOKEVIRTUAL scala/scratch/T$Inner.scala$scratch$T$Inner$$$outer ()Lscala/scratch/HasGlobal;
    INVOKEVIRTUAL scala/scratch/HasGlobal.global ()Lscala/scratch/Global;
    ARETURN
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x1001
  public synthetic scala$scratch$T$Inner$$$outer()Lscala/scratch/HasGlobal;
    ALOAD 0
    GETFIELD scala/scratch/T$Inner.$outer : Lscala/scratch/HasGlobal;
    ARETURN
    MAXSTACK = 1
    MAXLOCALS = 1
}

testV explores if there a further benefit from avoiding the outer pointer traversal, at the cost of a larger footprint for Typer.

[info] Benchmark            Mode  Cnt   Score   Error  Units
[info] CakeBenchmark.testT  avgt   20   8.100 ± 0.558  ns/op
[info] CakeBenchmark.testU  avgt   20  11.655 ± 0.273  ns/op
[info] CakeBenchmark.testV  avgt   20   7.162 ± 0.091  ns/op

It's difficult to make Analyzer itself a class, rather than a trait, because of the pattern of usage. But the HasGlobal change might be feasible without major refactoring of Typers.scala

retronym avatar Sep 05 '18 04:09 retronym