- Notifications
You must be signed in to change notification settings - Fork 14
Open
Labels
Milestone
Description
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