Discuss ideas for improving compiler performance in this issue. Break out detailed issues and use the t:performance label.
-XX:MaxInlineLevel=18 seems to help)The numbers reported in https://github.com/scala/scala/pull/5785#issuecomment-289610150 are terrific. Looking forward to this.
Misc items to optimize:
ClassSymbol.name calling flattenedName: https://github.com/scala/scala/compare/2.12.x...retronym:faster/flattenedname?expand=1$outer field show as signficant in the profiles of rawInfo. Collapse SymbolContextApiImpl into Symbol?TermSymbol.validAliasFlags should be a constant, not a field.Symbol.rawname rather than have identical field/accessors in TermSymbol and TypeSymbol?isOverridingSymbol (actually, we should key this cache by run id for correctness, anyway.)coevolveSym if pre1 eq pre https://github.com/scala/scala/compare/2.12.x...retronym:faster/coevolve?expand=1originalOwner, as called via isEffectivelyFinal <- rebind <- mapOver. Find fast paths through here.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)FindMemberBase.shortCircuit if we've already detected that findAll == true: https://github.com/scala/scala/compare/2.12.x...retronym:faster/findmemberBoxesRuntime.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.List.eq rather than List.== in LazyTreeCopier: https://github.com/retronym/scala/tree/faster/treecopy@tailrec method that internally while-ish: https://github.com/retronym/scala/tree/faster/weakhashset. x86 level profiling suggested (to my admittedly untraied eye) that the internal method wasn't being inlined. Look at -XX:+PrintInlining output to check this theory. asSeenFrom by short-circuting overriding checks in findMember / OverridingPairs. https://github.com/retronym/scala/tree/faster/findmember-approx. Perhaps even add a version of matchesType that internally calls asSeenFrom on symbol infos (which might also avoid the need to clone symbols just to create a new MethodType)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.
Various (ongoing/planned) efforts:
More ambitious:
(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>
Most helpful comment
The numbers reported in https://github.com/scala/scala/pull/5785#issuecomment-289610150 are terrific. Looking forward to this.