Skip to content

Rework tree transforms #3366

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Oct 26, 2017
Merged

Conversation

odersky
Copy link
Contributor

@odersky odersky commented Oct 22, 2017

Based on #3350.

@odersky
Copy link
Contributor Author

odersky commented Oct 22, 2017

test performance please

@dottybot
Copy link
Member

performance test scheduled: 1 job(s) in queue, 0 running.

@dottybot
Copy link
Member

Performance test finished successfully:

Visit http://dotty-bench.epfl.ch/3366/ to see the changes.

Benchmarks is based on merging with master (0438e48)

@odersky
Copy link
Contributor Author

odersky commented Oct 23, 2017

test performance please

@dottybot
Copy link
Member

performance test scheduled: 2 job(s) in queue, 1 running.

@dottybot
Copy link
Member

Performance test finished successfully:

Visit http://dotty-bench.epfl.ch/3366/ to see the changes.

Benchmarks is based on merging with master (e1365d9)

@odersky
Copy link
Contributor Author

odersky commented Oct 23, 2017

test performance please

@dottybot
Copy link
Member

performance test scheduled: 1 job(s) in queue, 0 running.

@dottybot
Copy link
Member

Performance test finished successfully:

Visit http://dotty-bench.epfl.ch/3366/ to see the changes.

Benchmarks is based on merging with master (e1365d9)

@smarter
Copy link
Member

smarter commented Oct 23, 2017

Could you give a list of differences between TreeTransformer and SuperPhase?

* This is an evolution of the previous "TreeTransformers.scala", which was written by @DarkDimius and
* is described in his thesis.
*/
object SuperPhase {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bikeshed: I'm not a big fan of the name SuperPhase since super already has a very specific meaning in Scala. Some possible alternatives: MegaPhase,FusedPhase, JoinedPhase.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, let's go with MegaPhase. I like the sound of that :-)

}

{ implicit val ctx = nestedCtx
tag match {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be tag: @switch


/** Transform full tree using all phases in this group that have idxInGroup >= start */
def transformTree(tree: Tree, start: Int)(implicit ctx: Context): Tree = {
val tag = tree.tag
Copy link
Member

@smarter smarter Oct 23, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could avoid the megamorphic dispatch here if we had something like:

abstract class Tree[-T >: Untyped](val tag: TreeTag) ...
// ...
class Ident(...) extends Tree(Tag.Ident) with ...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but at the cost of one field per tree. It might still be worth it, though. One needs to experiment.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have benchmarks now that support this optimization?

@DarkDimius
Copy link
Contributor

I should be able to review this on Wednesday.
Would you kindly give me a short summary of what motivated the changes in last 3 commits? It will give a perspective for review.

@odersky
Copy link
Contributor Author

odersky commented Oct 24, 2017

The motivation from the change came from me trying to understand (again) TreeTransformers.scala when I did the work on running all transforms at group end. I noted that things were very convoluted, and repetitive and that, as a consequence it was hard to find the right way to do changes. For instance there was the issue that TreeTransforms, but not TeeTransformers used cpyBetweenPhases. I believe that was not intentional (or was it?), but rather caused by the fact that due to its length, TreeTransformers is hard to maintain. The new set of changes cuts the size of TreeTransformers to roughly half of what it was before and clearly separates actual implementation logic (~200loc) from initialization code.

There was also the motivation to play with the implementation a bit to see whether we could wring more performance out of it. To be able to judge performance, I believe we have to get #3350 in first and then compare. I am not yet sure performance is better or worse. But to play with it I felt it was necessary to simplify it first.

In detail, the changes can be summarized as follows:

MegaPhase is a simplified version of TreeTransformer

  • about half of the size
  • no need for separate TransformerInfo arguments
  • simpler wiring logic

The simplifications are largely due to three changes

  • use of function values, where wiring is done by a sort of staging
  • use of tree tags, which give a common index into tables of transformer lambdas
  • prepare... operations return a Context instead of a TreeTransform, so there are
    fewer "moving parts".

@odersky
Copy link
Contributor Author

odersky commented Oct 24, 2017

For both review and performance comparisons it would be best if we got #3350 in first.

Copy link
Contributor

@liufengyun liufengyun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is another base class MacroTransform in the code, which is only used once. I think it's not justified to have a separate concept for it. Is it possible to get rid of it as well?

def newLocation[T](): (Location[T], Store) = {
val elems1 = new Array[AnyRef](elems.length + 1)
System.arraycopy(elems, 0, elems1, 0, elems.length)
(new Location(elems.length), new Store(elems1))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For another PR: minor optimisations here to double the size of array and keep track of size?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be complicated because arrays are purely functional. I.e you have to copy anyway on an update.


final val TypedSplice = 40
final val ModuleDef = 41
final val ParsedTry = 42
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add a comment describing which tag numbers can be reused and why.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 It's not clear for me why are they reused.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no re-use: NumTypeTreeTags is a counter, not a tag. I'll add a comment to make that clearer.

type TreeTag = Int

object Tag {
final val Ident = 0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use the same tags that are used in tasty. WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could try, but then the switches will not be dense anymore, so will most likely not be implemented by a table switch.

Copy link
Contributor Author

@odersky odersky Oct 26, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, in that case unpickled trees would not have tags, so we could not use tags for e.g. typer.

@@ -357,6 +425,7 @@ object Trees {
case class Ident[-T >: Untyped] private[ast] (name: Name)
extends RefTree[T] {
type ThisTree[-T >: Untyped] = Ident[T]
def tag = Tag.Ident
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

final?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Final.


/** Transform full tree using all phases in this group that have idxInGroup >= start */
def transformTree(tree: Tree, start: Int)(implicit ctx: Context): Tree = {
val tag = tree.tag
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have benchmarks now that support this optimization?

val rhs = transformTree(tree1.rhs, start)
trans(cpy.DefDef(tree1)(tree1.name, tparams, vparamss, tpt, rhs))
}
transformDefDef(localContext)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're trying to get all possible performance here - localContext has extra branch that DefDef never needs.

else {
var clsMethods = clsMethodsCache.get(cls)
if (clsMethods eq null) {
clsMethods = cls.getDeclaredMethods
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

proposal: add map(_.getName) here and use contains instead of exists below.

private def newTransformerArray[T, R](
methName: String, elemFn: IndexedTransformer[T, R], last: Transformer[T, R]) = {
val trans = new Array[Transformer[T, R]](miniPhases.length + 1)
trans(miniPhases.length) = last
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:-) Now it's actually closer to version described in the thesis.

private val nodeTransformer: Array[Array[Transformer[Tree, Tree]]] =
Array.fill(Tag.NumTags)(otherNodeTransformer)

private def init(name: String, tag: TreeTag, ctf: IndexedTransformer[_, Context], ntf: IndexedTransformer[_, Tree]): Unit = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think init is too generic name and also counterintuitive given that it is called more than once.


final val TypedSplice = 40
final val ModuleDef = 41
final val ParsedTry = 42
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 It's not clear for me why are they reused.

@DarkDimius
Copy link
Contributor

I like the idea of stores inside Context instead of mutable state inside phases.

@DarkDimius
Copy link
Contributor

I've left comments on individual commits.

I'm very curious to see what impact on performance will manual handling of tags have compared to instanceOf tests.

SuperPhase is a simplified version of TreeTransformer

 - about half of the size
 - no need for separate TransformerInfo arguments
 - simpler wiring logic

The simplifications are largely due to two changes

 - use of function values, where wiring is done by a sort
   of staging
 - prepare... operations return a Context instead of a TreeTranform
... and provide a way to initialize contexts when phases are linked.
Convert phases that created new TreeTransforms before to
use the new Store concept instead of Properties.
and add @switch annotation to core match in it.
The switch on tags in transformTree was a lookup switch with the
previous distribution of tags. By moving tags that get eliminated
before pickling to the end we can turn it into a table switch.
@odersky odersky force-pushed the change-transform-phases-4 branch from e73974a to 0bd23f8 Compare October 26, 2017 09:31
@odersky
Copy link
Contributor Author

odersky commented Oct 26, 2017

test performance please

@dottybot
Copy link
Member

performance test scheduled: 2 job(s) in queue, 1 running.

Mixin changes the number of parameters of trait initializers and therefore needs relaxed typing. This was not discovered before because TreeTransformers did not use a time travelling tree copier.
@odersky odersky force-pushed the change-transform-phases-4 branch from 3a8cde2 to 5a41b3e Compare October 26, 2017 12:40
@odersky odersky merged commit 02252a8 into scala:master Oct 26, 2017
@dottybot
Copy link
Member

Performance test finished successfully:

Visit http://dotty-bench.epfl.ch/3366/ to see the changes.

Benchmarks is based on merging with master (07e4968)

odersky added a commit to dotty-staging/dotty that referenced this pull request Oct 26, 2017
scala#3366 did lose some performance relative to the previous tree transform scheme.

http://dotty-bench.epfl.ch/3366/

shows a loss of about 4% for scalapb and dotty. This commit is trying to get it
back, using two tweaks:

 - We don't call an empty context or node transformer, instead we represent such
   transformers now by null and return immediately if we hit a null transformer
 - After a transform, we do a test whether we still have a tree of the same tag before
   computing the treeTag of the transformed tree. This avoids the megamorphic dispatch
   in the common case where the tag is the same.
@odersky odersky mentioned this pull request Oct 26, 2017
@allanrenucci allanrenucci deleted the change-transform-phases-4 branch December 14, 2017 16:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants