scala-dev
scala-dev copied to clipboard
Can we avoid invokeinterface for Analyzer.global within Typer?
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