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

Reduce allocations of AsSeenFromMap / FindMember / SubstSymMap

Open retronym opened this issue 7 years ago • 4 comments
trafficstars

image

It might be worth having a one-element cache in Global for each of these. We'd need to make them reusable (ie, the fields become vars and we add a reset method to assign new values).

The code in these can re-enter, at least through lazy type completers, so we either need to keep a stack of reusable ones, or simply just allocate for the re-entrant cases.

retronym avatar May 08 '18 07:05 retronym

Here's what I've got in mind. https://github.com/scala/scala/compare/9766d5d400d71e53615a66016e7899ddd1086c8d...retronym:faster/one-elem-cache

retronym avatar May 09 '18 14:05 retronym

I've taken a look at what would be needed to have the VM do this for us, with inlining and escape analysis.

One can use JITWatch:

object Test {
  val g = new scala.tools.nsc.Global(new scala.tools.nsc.Settings)
  g.settings.usejavacp.value = true
  import g._

  def main(args: Array[String])	{
    new Run()
    while (true) {
      test()
    }
  }

  def test() {
    typeOf[String].members
    typeOf[String].member(TermName("charAt"))
    typeOf[String].hasNonPrivateMember(TermName("charAt"))
  }
}
% qscalac sandbox/test.scala
% (qscala -J-XX:+UnlockDiagnosticVMOptions -J-XX:+TraceClassLoading -J-XX:+LogCompilation -J-XX:LogFile=/tmp/hotspot.log -J-XX:+PrintAssembly Test 2>&1) > /tmp/all.log

% cd /code; git clone jitwatch/jitwatch; cd jitwatch
% export MAVEN_OPTS="-Xmx4G"
% mvn clean compile test exec:java
  • Configure JitWatch with the location of the Scala sources and binaries:

image

  • Open Log /tmp/hotspot.log
  • Start
  • Navigate to scala.reflect.internal.Types$Type / findMember

image

  • 'Chain'

For escape analysis to kick in, we need FindMember.{<init>, apply, searchConcreteThenDeferred, walkBaseClasses, etc} all to inline, so that the FindMember instance doesn't escape outside the inlined unit of code.

With default VM options, we find this doesn't happen. Typically the reason in "already compiled into a big method".

image

This means that a helper method within FindMember had previously been chosen as the root of a JIT compilation task, and ended up with native code that exceeds a threshold in the JIT policy which prevents it from also being inlined into the call-tree that is the focus of the current JIT compilation task.

We can force inlining with a compiler control hint:

scala -J-XX:CompileCommand='inline scala.reflect.internal.tpe.FindMembers$FindMemberBase::*' ...

In which case we eventually can see escape analysis kick in:

image

(The -Allocs button in the JITWatch GUI shows all successfully eliminated allocations found in the compilation log.

Graal EE does a bit better at getting inlining some, but not all of these instances. Currently, HotSpot's profiling doesn't gather data about how much an allocation site contributes to the allocation rate of short lived objects. If it did, Graal could conceivably try harder to inline the allocation away.

retronym avatar Jul 24 '18 00:07 retronym

I feel like I've seen PRs and 2.12/2.13 differences around this (from @diesalbla and @hrhino's handiwork?). Is this done / happy to close it now?

dwijnand avatar Nov 04 '20 16:11 dwijnand

@dwijnand So, here are some Pull Requests done to reduce the number of objects created:

  • https://github.com/scala/scala/pull/6592
  • https://github.com/scala/scala/pull/8681
  • https://github.com/scala/scala/pull/8468
  • https://github.com/scala/scala/pull/8306
  • https://github.com/scala/scala/pull/8487

One last open Pull Request, https://github.com/scala/scala/pull/9039, should further reduce the number of SubstSymMap instances created, by reusing mutable ones.

diesalbla avatar Nov 05 '20 10:11 diesalbla