Skip to content

Can we avoid invokeinterface for Analyzer.global within Typer? #554

Open
@retronym

Description

@retronym

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions