Scala-dev: Compiler performance

Created on 2 Mar 2017  路  11Comments  路  Source: scala/scala-dev

Discuss ideas for improving compiler performance in this issue. Break out detailed issues and use the t:performance label.

Measure

  • [x] Create a suite of compiler benchmarks
  • [x] Run these benchmarks on each merge with benchq
  • [x] [Visualize](https://scala-ci.typesafe.com/grafana/dashboard/db/scala-benchmark) the results
  • [x] Add additional profiling hooks into the compiler

Tune

  • [x] [Experiment](https://github.com/scala/scala/pull/5785) with changes to the compiler to improve performance around 30%
  • [x] Polish up these changes and apply them to 2.12.x or 2.13.x as appropriate
  • [ ] Find an optimal set of JVM parameters (e.g. -XX:MaxInlineLevel=18 seems to help)
performance theme

Most helpful comment

The numbers reported in https://github.com/scala/scala/pull/5785#issuecomment-289610150 are terrific. Looking forward to this.

All 11 comments

The numbers reported in https://github.com/scala/scala/pull/5785#issuecomment-289610150 are terrific. Looking forward to this.

Misc items to optimize:

  • [ ] Avoid varargs array boxing in ClassSymbol.name calling flattenedName: https://github.com/scala/scala/compare/2.12.x...retronym:faster/flattenedname?expand=1
  • [ ] Why does access to the $outer field show as signficant in the profiles of rawInfo. Collapse SymbolContextApiImpl into Symbol?
  • [ ] TermSymbol.validAliasFlags should be a constant, not a field.
  • [ ] De-abstract Symbol.rawname rather than have identical field/accessors in TermSymbol and TypeSymbol?
  • [ ] Avoid lazy val for isOverridingSymbol (actually, we should key this cache by run id for correctness, anyway.)
  • [ ] Avoid call to coevolveSym if pre1 eq pre https://github.com/scala/scala/compare/2.12.x...retronym:faster/coevolve?expand=1
  • [ ] Some profiles show lots of work in originalOwner, as called via isEffectivelyFinal <- rebind <- mapOver. Find fast paths through here.
  • [ ] Avoid invokeinterface in mapOver (improve the inliner #388 or manually work around: https://github.com/scala/scala/compare/2.12.x...retronym:faster/mapoverinvokeinterface?expand=1)
  • [ ] Speed up HashMap by avoiding invokeinterface on hot paths. #389
  • [ ] Avoid virtual call to FindMemberBase.shortCircuit if we've already detected that findAll == true: https://github.com/scala/scala/compare/2.12.x...retronym:faster/findmember
  • [ ] Flush out remaining ineffeciencies due to BoxesRuntime.equals / Statics.anyHash by comparing compiler performance to https://github.com/scala/scala/compare/2.12.x...retronym:faster/boxesruntme?expand=1, which removes the special cases for numeric types. Use AnyRefMap or maybe even Java collections in hot paths. I think there is about 1% win remaining here.

Drive by comment: the unpickler traverses the index twice (https://github.com/scala/scala/blob/v2.12.2/src/reflect/scala/reflect/internal/pickling/UnPickler.scala#L82-L100). Maybe it's more efficient to store "children" and "symbolAnnotation" indicies in the first traversal.

Another misc to optimize (better after the backend refactoring https://github.com/scala/scala/pull/6012 is merged): The ClassWriter used ClassBType.jvmWiseLUB when computing stack map frames. The results are cached in the ASM ClassWriter, however we create a new ClassWriter for each class (and looking at its implementation, they cannot be re-used). It might make sense to cache the LUBs.

Here are some options that can disable parts of the compiler for max perfomance.

| Option | Downside |
|--------|----------|
| -no-specialization | More boxing of callsites using Tuple2, Function0/1/2, etc |
| -opt:l:none | Default settings still run a DCE local optimization (why?) |
| -Yno-generic-signatures | javac won't understand Scala generics |

Collectively, these give a 0.88x change to compile times for the scalap benchmark. These are areas we could look at optimizing so they don't incur such a penalty in the first place.

Default settings still run a DCE local optimization (why?)

it's to avoid some ugly / unexpected bytecode. without it

def f(b: Boolean) = { if (b) throw new Exception(); 1 }

gives

    ILOAD 1
    IFEQ L1
    NEW java/lang/Exception
    DUP
    INVOKESPECIAL java/lang/Exception.<init> ()V
    ATHROW
    NOP
    NOP
    ATHROW

our code gen produces the following (seen by enabling AsmUtils.traceClassEnabled / traceClassPattern):

    ILOAD 1
    IFEQ L2
    NEW java/lang/Exception
    DUP
    INVOKESPECIAL java/lang/Exception.<init> ()V
    ATHROW
    GOTO L4
   L4
    ICONST_1
    IRETURN

But then the classfile writer replaces the GOTO with NOP; ... NOP; ATHROW (with the same bytecode size). See comment here.

You can get more nops, for example with def f = { ???; println("hi" + 1) }

Jason points out we could flag methods where we insert ATHROW after a genLoad of type Nothing, and run DCE only on those. We can use jardiff to see if there are other cases where DCE does some work.

Jason points out we could flag methods where we insert ATHROW after a genLoad of type Nothing, and run DCE only on those.

https://github.com/scala/scala/pull/6044

Various (ongoing/planned) efforts:

  • [ ] symbol table reuse
  • [ ] figure out why compiler is slower when run in sbt
  • [ ] (allow/document how to) disable specialization for code bases that don't need it
  • [ ] backend IO pipelining

More ambitious:

  • [ ] use something like dotty's type assigner after type checking instead of full typer (erasure, pattern matcher make heavy use of typer where a more focussed solution is possible)
  • [ ] mutable trees (reduce GC / tree copying)
  • [ ] optimize typing transforms (more lightweight modeling of scope than a full instance of typer)

(allow/document how to) disable specialization for code bases that don't need it

It would be really, really nice if we could disable specialization in the entire Scala.js ecosystem, including on the standard library itself.

(allow/document how to) disable specialization for code bases that don't need it

It would be great to turn it off on Native as well.

Closing for lack of recent activity.</stale-bot>

Was this page helpful?
0 / 5 - 0 ratings

Related issues

lrytz picture lrytz  路  4Comments

retronym picture retronym  路  7Comments

adriaanm picture adriaanm  路  3Comments

lrytz picture lrytz  路  4Comments

retronym picture retronym  路  5Comments