8000 Compiler performance improvements around Symbols and Types by retronym · Pull Request #5823 · scala/scala · GitHub
[go: up one dir, main page]

Skip to content

Compiler performance improvements around Symbols and Types #5823

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 16 commits into from
May 24, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/compiler/scala/tools/nsc/ast/parser/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -829,7 +829,7 @@ self =>
def mkNamed(args: List[Tree]) = if (isExpr) args map treeInfo.assignmentToMaybeNamedArg else args
val arguments = right match {
case Parens(args) => mkNamed(args)
case _ => List(right)
case _ => right :: Nil
Copy link
Contributor

Choose a reason for hiding this comment

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

the first is more readable, is the second that much faster?

Copy link
Member Author
@retronym retronym Apr 20, 2017

Choose a reason for hiding this comment

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

Yeah, we're lacking an optimization on List.apply to avoid boxing an array to pass as varargs. We've got a ticket open for that improvement, but in the meantime I'm happy enough with the readability of :: to use it in the few places where this makes a material difference.

}
if (isExpr) {
if (treeInfo.isLeftAssoc(op)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -613,11 +613,11 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
annotatedInline = info.annotatedInline,
annotatedNoInline = info.annotatedNoInline)
if (methodSym.isMixinConstructor)
List((staticMethodSignature, staticMethodInfo))
(staticMethodSignature, staticMethodInfo) :: Nil
else
List((signature, info), (staticMethodSignature, staticMethodInfo))
(signature, info) :: (staticMethodSignature, staticMethodInfo) :: Nil
} else
List((signature, info))
(signature, info) :: Nil
}
}).toMap

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1089,7 +1089,8 @@ abstract class ClassfileParser {
cls setInfo completer
mod setInfo completer
mod.moduleClass setInfo loaders.moduleClassLoader
List(cls, mod.moduleClass) foreach (_.associatedFile = file)
cls.associatedFile = file
Copy link
Contributor

Choose a reason for hiding this comment

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

if you want to retain the previous structure, you could probably

(cls.associatedFile, mod.moduleClass.associatedFile) = (file, file)

mod.moduleClass.associatedFile = file
(cls, mod)
}

Expand Down
4 changes: 2 additions & 2 deletions src/compiler/scala/tools/nsc/transform/Erasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@ abstract class Erasure extends InfoTransform
class ComputeBridges(unit: CompilationUnit, root: Symbol) {

class BridgesCursor(root: Symbol) extends overridingPairs.Cursor(root) {
override def parents = List(root.info.firstParent)
override def parents = root.info.firstParent :: Nil
// Varargs bridges may need generic bridges due to the non-repeated part of the signature of the involved methods.
// The vararg bridge is generated during refchecks (probably to simplify override checking),
// but then the resulting varargs "bridge" method may itself need an actual erasure bridge.
Expand Down Expand Up @@ -656,7 +656,7 @@ abstract class Erasure extends InfoTransform
private def adaptMember(tree: Tree): Tree = {
//Console.println("adaptMember: " + tree);
tree match {
case Apply(ta @ TypeApply(sel @ Select(qual, name), List(targ)), List())
case Apply(ta @ TypeApply(sel @ Select(qual, name), targ :: Nil), List())
if tree.symbol == Any_asInstanceOf =>
val qual1 = typedQualifier(qual, NOmode, ObjectTpe) // need to have an expected type, see #3037
// !!! Make pending/run/t5866b.scala work. The fix might be here and/or in unbox1.
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/scala/tools/nsc/typechecker/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -633,7 +633,7 @@ trait Implicits {
val itree2 = if (!isView) fallback else pt match {
case Function1(arg1, arg2) =>
typed1(
atPos(itree0.pos)(Apply(itree1, List(Ident(nme.argument) setType approximate(arg1)))),
atPos(itree0.pos)(Apply(itree1, Ident(nme.argument).setType(approximate(arg1)) :: Nil)),
EXPRmode,
approximate(arg2)
) match {
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/scala/tools/nsc/typechecker/Typers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1859,7 +1859,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
if (noSerializable) Nil
else {
clazz.makeSerializable()
List(TypeTree(SerializableTpe) setPos clazz.pos.focus)
TypeTree(SerializableTpe).setPos(clazz.pos.focus) :: Nil
}
)
})
Expand Down
16 changes: 9 additions & 7 deletions src/reflect/scala/reflect/internal/Symbols.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2323,8 +2323,9 @@ trait Symbols extends api.Symbols { self: SymbolTable =>

private def matchingSymbolInternal(site: Type, candidate: Symbol): Symbol = {
def qualifies(sym: Symbol) = !sym.isTerm || ((site memberType this) matches (site memberType sym))
//OPT cut down on #closures by special casing non-overloaded case
if (candidate.isOverloaded) candidate filter qualifies
//OPT Fast past for NoSymbol. Cut down on #closures by special casing non-overloaded case
if (candidate == NoSymbol) NoSymbol
else if (candidate.isOverloaded) candidate filter qualifies
else if (qualifies(candidate)) candidate
else NoSymbol
}
Expand Down Expand Up @@ -2374,15 +2375,16 @@ trait Symbols extends api.Symbols { self: SymbolTable =>

/** Returns all symbols overridden by this symbol. */
final def allOverriddenSymbols: List[Symbol] = {
def loop(xs: List[Symbol]): List[Symbol] = xs match {
case Nil => Nil
@tailrec
def loop(xs: List[Symbol], result: List[Symbol]): List[Symbol] = xs match {
case Nil => result
case x :: xs =>
Copy link
Contributor

Choose a reason for hiding this comment

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

minor: could also be y :: ys to avoid name collision with parent match

overriddenSymbol(x) match {
case NoSymbol => loop(xs)
case sym => sym :: loop(xs)
case NoSymbol => loop(xs, result)
case sym => loop(xs, sym :: result)
}
}
if (isOverridingSymbol) loop(owner.ancestors) else Nil
if (isOverridingSymbol) loop(owner.ancestors, Nil) else Nil
}

/** Equivalent to allOverriddenSymbols.nonEmpty, but more efficient. */
Expand Down
5 changes: 4 additions & 1 deletion src/reflect/scala/reflect/internal/TreeGen.scala
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,10 @@ abstract class TreeGen {

val tree = Select(pkgQualifier, sym)
if (pkgQualifier.tpe == null) tree
else tree setType (qual.tpe memberType sym)
else tree setType {
if (sym.rawowner == ObjectClass || sym.rawowner == AnyClass) sym.tpeHK.normalize // opt for asInstanceOf
else (qual.tpe memberType sym)
}
}
}

Expand Down
103 changes: 62 additions & 41 deletions src/reflect/scala/reflect/internal/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1087,7 +1087,7 @@ trait Types
override def baseTypeSeq: BaseTypeSeq = supertype.baseTypeSeq
override def baseTypeSeqDepth: Depth = supertype.baseTypeSeqDepth
override def baseClasses: List[Symbol] = supertype.baseClasses
}
override def boundSyms: Set[Symbol] = emptySymbolSet}

/** A base class for types that represent a single value
* (single-types and this-types).
Expand Down Expand Up @@ -2129,15 +2129,27 @@ trait Types
//OPT specialize hashCode
override final def computeHashCode = {
import scala.util.hashing.MurmurHash3._
val hasArgs = args ne Nil
var h = productSeed
h = mix(h, pre.hashCode)
h = mix(h, sym.hashCode)
if (hasArgs)
finalizeHash(mix(h, args.hashCode()), 3)
else
finalizeHash(h, 2)
var length = 2
var elems = args
while (elems ne Nil) {
h = mix(h, elems.head.hashCode())
elems = elems.tail
length += 1
}
finalizeHash(h, length)
}
//OPT specialize equals
override final def equals(other: Any): Boolean = {
other match {
case otherTypeRef: TypeRef =>
pre.equals(otherTypeRef.pre) && sym.eq(otherTypeRef.sym) && sameElementsEquals(args, otherTypeRef.args)
case _ => false
}
}


// interpret symbol's info in terms of the type's prefix and type args
protected def relativeInfo: Type = appliedType(sym.info.asSeenFrom(pre, sym.owner), argsOrDummies)
Expand Down Expand Up @@ -3605,7 +3617,10 @@ trait Types
if (sym.isAliasType && sameLength(sym.info.typeParams, args) && !sym.lockOK)
throw new RecoverableCyclicReference(sym)

TypeRef(pre, sym, args)
if ((args eq Nil) && (pre eq NoPrefix))
sym.tpeHK // opt lean on TypeSymbol#tyconCache, rather than interning a type ref.
Copy link
Member

Choose a reason for hiding this comment

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

Could this be pushed further down into TypeRef.apply?

Copy link
Member Author

Choose a reason for hiding this comment

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

IIRC, we can't (easily) do that because tpeHK calls that TypeRef.apply, so we'd loop.

Copy link
Member

Choose a reason for hiding this comment

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

OK, I see.. We could clean up TypeRef construction one day.

else
TypeRef(pre, sym, args)
case _ =>
typeRef(pre, sym, args)
}
Expand Down Expand Up @@ -3786,7 +3801,7 @@ trait Types
private var uniques: util.WeakHashSet[Type] = _
private var uniqueRunId = NoRunId

protected def unique[T <: Type](tp: T): T = {
protected def unique[T <: Type](tp: T): T = {
Copy link
Member

Choose a reason for hiding this comment

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

if (Statistics.canEnable) Statistics.incCounter(rawTypeCount)
if (uniqueRunId != currentRunId) {
uniques = util.WeakHashSet[Type](initialUniquesCapacity)
Expand Down Expand Up @@ -3901,7 +3916,7 @@ trait Types
&& isRawIfWithoutArgs(sym)
)

def singletonBounds(hi: Type) = TypeBounds.upper(intersectionType(List(hi, SingletonClass.tpe)))
def singletonBounds(hi: Type) = TypeBounds.upper(intersectionType(hi :: SingletonClass.tpe :: Nil))

/**
* A more persistent version of `Type#memberType` which does not require
Expand Down Expand Up @@ -4241,38 +4256,44 @@ trait Types
*/
protected[internal] def specializesSym(preLo: Type, symLo: Symbol, preHi: Type, symHi: Symbol, depth: Depth): Boolean =
(symHi.isAliasType || symHi.isTerm || symHi.isAbstractType) && {
// only now that we know symHi is a viable candidate ^^^^^^^, do the expensive checks: ----V
require((symLo ne NoSymbol) && (symHi ne NoSymbol), ((preLo, symLo, preHi, symHi, depth)))

val tpHi = preHi.memberInfo(symHi).substThis(preHi.typeSymbol, preLo)

// Should we use memberType or memberInfo?
// memberType transforms (using `asSeenFrom`) `sym.tpe`,
// whereas memberInfo performs the same transform on `sym.info`.
// For term symbols, this ends up being the same thing (`sym.tpe == sym.info`).
// For type symbols, however, the `.info` of an abstract type member
// is defined by its bounds, whereas its `.tpe` is a `TypeRef` to that type symbol,
// so that `sym.tpe <:< sym.info`, but not the other way around.
//
// Thus, for the strongest (correct) result,
// we should use `memberType` on the low side.
//
// On the high side, we should use the result appropriate
// for the right side of the `<:<` above (`memberInfo`).
val tpLo = preLo.memberType(symLo)

debuglog(s"specializesSymHi: $preHi . $symHi : $tpHi")
debuglog(s"specializesSymLo: $preLo . $symLo : $tpLo")

if (symHi.isTerm)
(isSubType(tpLo, tpHi, depth) &&
(!symHi.isStable || symLo.isStable) && // sub-member must remain stable
(!symLo.hasVolatileType || symHi.hasVolatileType || tpHi.isWildcard)) // sub-member must not introduce volatility
else if (symHi.isAbstractType)
((tpHi.bounds containsType tpLo) &&
kindsConform(symHi :: Nil, tpLo :: Nil, preLo, symLo.owner))
else // we know `symHi.isAliasType` (see above)
tpLo =:= tpHi
val symHiInfo = symHi.info
if (symHi.isTerm && symHiInfo == WildcardType) {
// OPT fast path (avoiding tpLo.mmeberType) for wildcards which appear here frequently in the search for implicit views.
!symHi.isStable || symLo.isStable // sub-member must remain stable
} else {
// only now that we know symHi is a viable candidate, do the expensive checks: ----V
require((symLo ne NoSymbol) && (symHi ne NoSymbol), ((preLo, symLo, preHi, symHi, depth)))

val tpHi = symHiInfo.asSeenFrom(preHi, symHi.owner).substThis(preHi.typeSymbol, preLo)

// Should we use memberType or memberInfo?
// memberType transforms (using `asSeenFrom`) `sym.tpe`,
// whereas memberInfo performs the same transform on `sym.info`.
// For term symbols, this ends up being the same thing (`sym.tpe == sym.info`).
// For type symbols, however, the `.info` of an abstract type member
// is defined by its bounds, whereas its `.tpe` is a `TypeRef` to that type symbol,
// so that `sym.tpe <:< sym.info`, but not the other way around.
//
// Thus, for the strongest (correct) result,
// we should use `memberType` on the low side.
//
// On the high side, we should use the result appropriate
// for the right side of the `<:<` above (`memberInfo`).
val tpLo = preLo.memberType(symLo)

debuglog(s"specializesSymHi: $preHi . $symHi : $tpHi")
debuglog(s"specializesSymLo: $preLo . $symLo : $tpLo")

if (symHi.isTerm)
(isSubType(tpLo, tpHi, depth) &&
(!symHi.isStable || symLo.isStable) && // sub-member must remain stable
(!symLo.hasVolatileType || symHi.hasVolatileType || tpHi.isWildcard)) // sub-member must not introduce volatility
else if (symHi.isAbstractType)
((tpHi.bounds containsType tpLo) &&
kindsConform(symHi :: Nil, tpLo :: Nil, preLo, symLo.owner))
else // we know `symHi.isAliasType` (see above)
tpLo =:= tpHi
}
}

/** A function implementing `tp1` matches `tp2`. */
Expand Down
7 changes: 3 additions & 4 deletions src/reflect/scala/reflect/internal/tpe/GlbLubs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,7 @@ private[internal] trait GlbLubs {
/** Eliminate from list of types all elements which are a supertype
* of some other element of the list. */
private def elimSuper(ts: List[Type]): List[Type] = ts match {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't claim to understand it, but I have a question:
This seems to be an O(n^2) operation (i.e. for every element search all remaining). Would it be faster to cache the parents in a Set and check the set for inconsistencies for the previously encountered parents instead?
That could make it linear, I think.
(would probably be slower if the number of elements is small)

Copy link
Member Author

Choose a reason for hiding this comment

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

Type-s aren't amenable to use in a HashSet because =:= isn't compatible with hashCode. This code here is actually using subtyping, which makes it harder again to find ways to beat O(N^2).

case List() => List()
case List(t) => List(t)
case List() | List(_) => ts
case t :: ts1 =>
val rest = elimSuper(ts1 filter (t1 => !(t <:< t1)))
if (rest exists (t1 => t1 <:< t)) rest else t :: rest
Expand All @@ -195,8 +194,8 @@ private[internal] trait GlbLubs {
* of some other element of the list. */
private def elimSub(ts: List[Type], depth: Depth): List[Type] = {
def elimSub0(ts: List[Type]): List[Type] = ts match {
case List() => List()
case List(t) => List(t)
case List() => ts
Copy link
Contributor

Choose a reason for hiding this comment

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

could probably be:

case List() | List(_) => ts

instead

case List(t) => ts
case t :: ts1 =>
val rest = elimSub0(ts1 filter (t1 => !isSubType(t1, t, depth.decr)))
if (rest exists (t1 => isSubType(t, t1, depth.decr))) rest else t :: rest
Expand Down
12 changes: 6 additions & 6 deletions src/reflect/scala/reflect/internal/tpe/TypeComparers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,9 @@ trait TypeComparers {
// combination of { tp1, tp2 } { is, is not } an AnnotatedType - this because the
// logic of "annotationsConform" is arbitrary and unknown.
private def isSameType1(tp1: Type, tp2: Type): Boolean = typeRelationPreCheck(tp1, tp2) match {
case state if state.isKnown => state.booleanValue
case _ if typeHasAnnotations(tp1) || typeHasAnnotations(tp2) => sameAnnotatedTypes(tp1, tp2)
case _ => isSameType2(tp1, tp2)
case state if state.isKnown => state.booleanValue
case _ if tp1.isInstanceOf[AnnotatedType] || tp2.isInstanceOf[AnnotatedType] => sameAnnotatedTypes(tp1, tp2)
case _ => isSameType2(tp1, tp2)
}

private def isSameHKTypes(tp1: Type, tp2: Type) = (
Expand Down Expand Up @@ -325,9 +325,9 @@ trait TypeComparers {
}

private def isSubType1(tp1: Type, tp2: Type, depth: Depth): Boolean = typeRelationPreCheck(tp1, tp2) match {
case state if state.isKnown => state.booleanValue
case _ if typeHasAnnotations(tp1) || typeHasAnnotations(tp2) => annotationsConform(tp1, tp2) && (tp1.withoutAnnotations <:< tp2.withoutAnnotations)
case _ => isSubType2(tp1, tp2, depth)
case state if state.isKnown => state.booleanValue
case _ if tp1.isInstanceOf[AnnotatedType] || tp2.isInstanceOf[AnnotatedType] => annotationsConform(tp1, tp2) && (tp1.withoutAnnotations <:< tp2.withoutAnnotations)
case _ => isSubType2(tp1, tp2, depth)
}

private def isPolySubType(tp1: PolyType, tp2: PolyType): Boolean = {
Expand Down
13 changes: 9 additions & 4 deletions src/reflect/scala/reflect/internal/tpe/TypeMaps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,16 @@ private[internal] trait TypeMaps {
case tr @ TypeRef(pre, sym, args) =>
val pre1 = this(pre)
val args1 = (
if (trackVariance && args.nonEmpty && !variance.isInvariant && sym.typeParams.nonEmpty)
mapOverArgs(args, sym.typeParams)
else
if (trackVariance && args.nonEmpty && !variance.isInvariant) {
val tparams = sym.typeParams
Copy link
Contributor

Choose a reason for hiding this comment

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

minor: to make it more obvious what happened to the sym.typeParams.nonEmpty part of the if, this could probably be simplified to

if (sym.typeParams.nonEmpty) mapOverArgs(args, sym.typeParams)
else args mapConserve this

if (tparams.isEmpty)
args mapConserve this
else
mapOverArgs(args, tparams)
} else {
args mapConserve this
)
}
)
if ((pre1 eq pre) && (args1 eq args)) tp
else copyTypeRef(tp, pre1, tr.coevolveSym(pre1), args1)
case ThisType(_) => tp
Expand Down
13 changes: 13 additions & 0 deletions src/reflect/scala/reflect/internal/util/Collections.scala
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,19 @@ trait Collections {
head
}

final def sameElementsEquals(thiss: List[AnyRef], that: List[AnyRef]): Boolean = {
// Probably immutable, so check reference identity first (it's quick anyway)
(thiss eq that) || {
Copy link
Contributor

Choose a reason for hiding this comment

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

question: this inlining of thiss.sameElements(that) seems like a micro-optimization to my untrained eye :)

Copy link
Member Author

Choose a reason for hiding this comment

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

The key difference is using .equals rather than .==. The latter is routed through BoxesRuntime.equals which needs to perform a few type tests on the operands before delegating to equals in order to paper over the differences between primitive and boxed numeric equality.

var these = thiss
var those = that
while (!these.isEmpty && !those.isEmpty && these.head.equals(those.head)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

minor: could probably be while (these.nonEmpty && those.nonEmpty

Copy link
Member Author

Choose a reason for hiding this comment

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

I've got a bit of an aversion to nonEmpty (a mixed in trait method) in hot code because in some cases it is harder for JIT to devirtualize and inline. Not sure whether that's the case here.

these = these.tail
those = those.tail
}
these.isEmpty && those.isEmpty
}
}

final def collectFirst[A, B](as: List[A])(pf: PartialFunction[A, B]): Option[B] = {
@tailrec
def loop(rest: List[A]): Option[B] = rest match {
Expand Down
12 changes: 6 additions & 6 deletions src/reflect/scala/reflect/internal/util/WeakHashSet.scala
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ final class WeakHashSet[A <: AnyRef](val initialCapacity: Int, val loadFactor: D
case null => null.asInstanceOf[A]
case _ => {
val entryElem = entry.get
if (elem == entryElem) entryElem
if (elem.equals(entryElem)) entryElem
else linkedListLoop(entry.tail)
}
}
Expand Down Expand Up @@ -185,7 +185,7 @@ final class WeakHashSet[A <: AnyRef](val initialCapacity: Int, val loadFactor: D
case null => add()
case _ => {
val entryElem = entry.get
if (elem == entryElem) entryElem
if (elem.equals(entryElem)) entryElem
else linkedListLoop(entry.tail)
}
}
Expand All @@ -211,9 +211,9 @@ final class WeakHashSet[A <: AnyRef](val initialCapacity: Int, val loadFactor: D

@tailrec
def linkedListLoop(entry: Entry[A]): Unit = entry match {
case null => add()
case _ if (elem == entry.get) => ()
case _ => linkedListLoop(entry.tail)
case null => add()
case _ if elem.equals(entry.get) => ()
case _ => linkedListLoop(entry.tail)
}

linkedListLoop(oldHead)
Expand All @@ -238,7 +238,7 @@ final class WeakHashSet[A <: AnyRef](val initialCapacity: Int, val loadFactor: D
@tailrec
def linkedListLoop(prevEntry: Entry[A], entry: Entry[A]): Unit = entry match {
case null => ()
case _ if (elem == entry.get) => remove(bucket, prevEntry, entry)
case _ if elem.equals(entry.get) => remove(bucket, prevEntry, entry)
case _ => linkedListLoop(entry, entry.tail)
}

Expand Down
0