Skip to content
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

Improve performance of loading used names from persisted Analysis file #995

Merged
merged 9 commits into from
Jul 29, 2021
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,10 @@ final case class ModifiedNames(names: Set[UsedName]) {
names.flatMap(n => n.scopes.asScala.map(n.name -> _))

def isModified(usedName: UsedName): Boolean =
usedName.scopes.asScala.exists(scope => lookupMap.contains(usedName.name -> scope))
usedName.scopes.asScala.exists(scope => isModifiedRaw(usedName.name, scope))

def isModifiedRaw(name: String, scope: UseScope): Boolean =
lookupMap.contains(name -> scope)

override def toString: String =
s"ModifiedNames(changes = ${names.mkString(", ")})"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,28 @@ package inc
import java.io.File
import java.nio.file.{ Files, Path, Paths }
import java.util.{ EnumSet, UUID }
import java.util.concurrent.atomic.{ AtomicBoolean }
import java.util.concurrent.atomic.AtomicBoolean
import sbt.internal.inc.Analysis.{ LocalProduct, NonLocalProduct }
import sbt.internal.inc.JavaInterfaceUtil.EnrichOption
import sbt.util.{ InterfaceUtil, Level, Logger }
import sbt.util.InterfaceUtil.{ jo2o, t2 }

import scala.collection.JavaConverters._
import scala.collection.mutable
import scala.util.control.NonFatal
import xsbti.{ FileConverter, Position, Problem, Severity, UseScope, VirtualFile, VirtualFileRef }
import xsbt.api.{ APIUtil, HashAPI, NameHashing }
import xsbti.api._
import xsbti.compile.{
AnalysisContents,
AnalysisStore => XAnalysisStore,
CompileAnalysis,
CompileProgress,
DependencyChanges,
IncOptions,
MiniOptions,
MiniSetup,
Output,
AnalysisStore => XAnalysisStore,
ClassFileManager => XClassFileManager
}
import xsbti.compile.analysis.{ ReadStamps, Stamp => XStamp }
Expand Down Expand Up @@ -626,7 +628,7 @@ private final class AnalysisCallback(
private[this] val objectApis = new TrieMap[String, ApiInfo]
private[this] val classPublicNameHashes = new TrieMap[String, Array[NameHash]]
private[this] val objectPublicNameHashes = new TrieMap[String, Array[NameHash]]
private[this] val usedNames = new RelationBuilder[String, UsedName]
private[this] val usedNames = new TrieMap[String, mutable.Set[UsedName]]
private[this] val unreporteds = new TrieMap[VirtualFileRef, ConcurrentLinkedQueue[Problem]]
private[this] val reporteds = new TrieMap[VirtualFileRef, ConcurrentLinkedQueue[Problem]]
private[this] val mainClasses = new TrieMap[VirtualFileRef, ConcurrentLinkedQueue[String]]
Expand Down Expand Up @@ -862,10 +864,12 @@ private final class AnalysisCallback(
()
}

def usedName(className: String, name: String, useScopes: EnumSet[UseScope]) =
usedNames.synchronized {
usedNames(className) = UsedName.make(name, useScopes)
}
def usedName(className: String, name: String, useScopes: EnumSet[UseScope]) = {
usedNames
.getOrElseUpdate(className, ConcurrentHashMap.newKeySet[UsedName].asScala)
.add(UsedName.make(name, useScopes))
()
}

override def enabled(): Boolean = options.enabled

Expand Down Expand Up @@ -905,11 +909,15 @@ private final class AnalysisCallback(
}

def getOrNil[A, B](m: collection.Map[A, Seq[B]], a: A): Seq[B] = m.get(a).toList.flatten

def addCompilation(base: Analysis): Analysis =
base.copy(compilations = base.compilations.add(compilation))

def addUsedNames(base: Analysis): Analysis = {
assert(base.relations.names.size == 0)
base.copy(relations = base.relations.addUsedNames(usedNames.result()))
assert(base.relations.names.isEmpty)
base.copy(
relations = base.relations.addUsedNames(UsedNames.fromMultiMap(usedNames))
)
}

private def companionsWithHash(className: String): (Companions, HashAPI.Hash, HashAPI.Hash) = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ private[inc] class MemberRefInvalidator(log: Logger, logRecompileOnMacro: Boolea
private final val NoInvalidation = (_: String) => Set.empty[String]
def get(
memberRef: Relation[String, String],
usedNames: Relation[String, UsedName],
usedNames: Relations.UsedNames,
apiChange: APIChange,
isScalaClass: String => Boolean
): String => Set[String] = apiChange match {
Expand Down Expand Up @@ -121,7 +121,7 @@ private[inc] class MemberRefInvalidator(log: Logger, logRecompileOnMacro: Boolea
}

private class NameHashFilteredInvalidator(
usedNames: Relation[String, UsedName],
usedNames: Relations.UsedNames,
memberRef: Relation[String, String],
modifiedNames: ModifiedNames,
isScalaClass: String => Boolean
Expand All @@ -131,17 +131,18 @@ private[inc] class MemberRefInvalidator(log: Logger, logRecompileOnMacro: Boolea
val dependent = memberRef.reverse(to)
filteredDependencies(dependent)
}

private def filteredDependencies(dependent: Set[String]): Set[String] = {
dependent.filter {
case from if isScalaClass(from) =>
val affectedNames = usedNames.forward(from).filter(modifiedNames.isModified)
if (affectedNames.isEmpty) {
if (!usedNames.hasAffectedNames(modifiedNames, from)) {
log.debug(
s"None of the modified names appears in source file of $from. This dependency is not being considered for invalidation."
)
false
} else {
log.debug(s"The following modified names cause invalidation of $from: $affectedNames")
val affectedNames = usedNames.affectedNames(modifiedNames, from)
log.debug(s"The following modified names cause invalidation of $from: [$affectedNames]")
true
}
case from =>
Expand Down
35 changes: 20 additions & 15 deletions internal/zinc-core/src/main/scala/sbt/internal/inc/Relations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,6 @@ trait Relations {
/** Internal source dependencies that depend on external source file `dep`. This includes both direct and inherited dependencies. */
def usesExternal(className: String): Set[String]

private[inc] def usedNames(className: String): Set[UsedName]

/**
* Records that the file `src` generates products `products`, has internal dependencies `internalDeps`,
* has external dependencies `externalDeps` and library dependencies `libraryDeps`.
Expand Down Expand Up @@ -140,7 +138,7 @@ trait Relations {
deps: Iterable[(VirtualFileRef, String, XStamp)]
): Relations

private[inc] def addUsedNames(data: Relation[String, UsedName]): Relations
private[inc] def addUsedNames(data: Relations.UsedNames): Relations

/** Concatenates the two relations. Acts naively, i.e., doesn't internalize external deps on added files. */
def ++(o: Relations): Relations
Expand Down Expand Up @@ -267,7 +265,7 @@ trait Relations {
/**
* Relation between source files and _unqualified_ term and type names used in given source file.
*/
private[inc] def names: Relation[String, UsedName]
private[inc] def names: Relations.UsedNames

private[inc] def copy(
srcProd: Relation[VirtualFileRef, VirtualFileRef] = srcProd,
Expand All @@ -276,12 +274,13 @@ trait Relations {
internalDependencies: InternalDependencies = internalDependencies,
externalDependencies: ExternalDependencies = externalDependencies,
classes: Relation[VirtualFileRef, String] = classes,
names: Relation[String, UsedName] = names,
names: Relations.UsedNames = names,
productClassName: Relation[String, String] = productClassName,
): Relations
}

object Relations {
type UsedNames = inc.UsedNames

/** Tracks internal and external source dependencies for a specific dependency type, such as direct or inherited.*/
private[inc] final class ClassDependencies(
Expand Down Expand Up @@ -318,7 +317,7 @@ object Relations {
internalDependencies = InternalDependencies.empty,
externalDependencies = ExternalDependencies.empty,
classes = Relation.empty,
names = Relation.empty,
names = UsedNames.fromMultiMap(Map.empty),
productClassName = Relation.empty
)

Expand All @@ -329,7 +328,7 @@ object Relations {
internalDependencies: InternalDependencies,
externalDependencies: ExternalDependencies,
classes: Relation[VirtualFileRef, String],
names: Relation[String, UsedName],
names: Relations.UsedNames,
productClassName: Relation[String, String]
): Relations =
new MRelationsNameHashing(
Expand All @@ -348,6 +347,7 @@ object Relations {
external: Relation[String, String]
): ClassDependencies =
new ClassDependencies(internal, external)

}

private[inc] object DependencyCollection {
Expand All @@ -368,7 +368,7 @@ private[inc] object DependencyCollection {
private[inc] object InternalDependencies {

/**
* Constructs an empty `InteralDependencies`
* Constructs an empty `InternalDependencies`
*/
def empty = InternalDependencies(Map.empty)
}
Expand Down Expand Up @@ -470,7 +470,7 @@ private class MRelationsNameHashing(
val internalDependencies: InternalDependencies,
val externalDependencies: ExternalDependencies,
val classes: Relation[VirtualFileRef, String],
val names: Relation[String, UsedName],
val names: Relations.UsedNames,
val productClassName: Relation[String, String]
) extends Relations {
def allSources: collection.Set[VirtualFileRef] = srcProd._1s
Expand Down Expand Up @@ -501,8 +501,6 @@ private class MRelationsNameHashing(
def externalDeps(className: String): Set[String] = externalClassDep.forward(className)
def usesExternal(className: String): Set[String] = externalClassDep.reverse(className)

private[inc] def usedNames(className: String): Set[UsedName] = names.forward(className)

def addProducts(src: VirtualFileRef, products: Iterable[VirtualFileRef]): Relations =
new MRelationsNameHashing(
srcProd ++ products.map(p => (src, p)),
Expand Down Expand Up @@ -563,15 +561,15 @@ private class MRelationsNameHashing(
productClassName,
)

private[inc] def addUsedNames(data: Relation[String, UsedName]): Relations = {
private[inc] def addUsedNames(data: Relations.UsedNames): Relations = {
new MRelationsNameHashing(
srcProd,
libraryDep,
libraryClassName,
internalDependencies,
externalDependencies,
classes,
names = if (names.forwardMap.isEmpty) data else names ++ data,
names = if (names.isEmpty) data else names ++ data,
productClassName,
)
}
Expand Down Expand Up @@ -626,7 +624,7 @@ private class MRelationsNameHashing(
internalDependencies: InternalDependencies = internalDependencies,
externalDependencies: ExternalDependencies = externalDependencies,
classes: Relation[VirtualFileRef, String] = classes,
names: Relation[String, UsedName] = names,
names: Relations.UsedNames = names,
productClassName: Relation[String, String] = productClassName,
): Relations = new MRelationsNameHashing(
srcProd,
Expand Down Expand Up @@ -657,6 +655,13 @@ private class MRelationsNameHashing(
if (r.forwardMap.isEmpty) "Relation [ ]"
else r.all.toSeq.map(kv => line_s(kv._1, kv._2)).sorted.mkString("Relation [\n", "", "]")
}
def usedNames_s(r: Relations.UsedNames) = {
if (r.isEmpty) "UsedNames [ ]"
else {
val all = r.iterator.flatMap { case (a, bs) => bs.iterator.map(b => (a, b)) }
all.map(kv => line_s(kv._1, kv._2)).toSeq.sorted.mkString("UsedNames [\n", "", "]")
}
}

override def toString: String = {
def deps_s(m: Map[_, Relation[_, _]]) =
Expand All @@ -671,7 +676,7 @@ private class MRelationsNameHashing(
| internalDependencies: ${deps_s(internalDependencies.dependencies)}
| externalDependencies: ${deps_s(externalDependencies.dependencies)}
| class names: ${relation_s(classes)}
| used names: ${relation_s(names)}
| used names: ${usedNames_s(names)}
| product class names: ${relation_s(productClassName)}
""".trim.stripMargin
}
Expand Down
Loading