Skip to content

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

@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