From f1698d2bf2a1eea316948050f35b99f0029eba89 Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Thu, 6 Jun 2019 20:05:31 -0700 Subject: [PATCH] Re-use metabuild scala instance layer At some point I noticed that projects with no scala sources in the build loaded significantly faster than projects that had even a single scala file -- no matter how simple that file was. This didn't really make sense to me because *.sbt files _do_ have to be compiled. I finally realized that classloading was a likely bottle neck because *.sbt files are compiled on the sbt classpath while *.scala files are compiled with a different classloader generated by the classloader cache. It then occurred to me that we could pre-fill the classloader cache with the scala layer of the sbt metabuild classloader. I found that compared to 1.3.0-M5, a project with a simple scala file in the project directory loaded about 2 seconds faster after this change. Even if there are no scala sources in the build.sbt, there is a similar performance improvement for running "sbt compile", which I found exited 2-3 seconds faster after this change. --- main-command/src/main/scala/sbt/State.scala | 41 ++++++++++++++++----- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/main-command/src/main/scala/sbt/State.scala b/main-command/src/main/scala/sbt/State.scala index f62a1aa75..f8a88a7c6 100644 --- a/main-command/src/main/scala/sbt/State.scala +++ b/main-command/src/main/scala/sbt/State.scala @@ -8,20 +8,14 @@ package sbt import java.io.File +import java.net.{ URL, URLClassLoader } import java.util.concurrent.Callable import sbt.internal.classpath.ClassLoaderCache import sbt.internal.inc.classpath.{ ClassLoaderCache => IncClassLoaderCache } -import sbt.util.Logger -import sbt.internal.util.{ - AttributeKey, - AttributeMap, - ErrorHandling, - ExitHook, - ExitHooks, - GlobalLogging -} import sbt.internal.util.complete.{ HistoryCommands, Parser } +import sbt.internal.util._ +import sbt.util.Logger /** * Data structure representing all command execution information. @@ -202,6 +196,10 @@ trait StateOps extends Any { } object State { + private class UncloseableURLLoader(cp: Seq[File], parent: ClassLoader) + extends URLClassLoader(Array.empty, parent) { + override def getURLs: Array[URL] = cp.map(_.toURI.toURL).toArray + } /** Indicates where command execution should resume after a failure.*/ val FailureWall = BasicCommandStrings.FailureWall @@ -344,6 +342,31 @@ object State { def initializeClassLoaderCache: State = { s.get(BasicKeys.extendedClassLoaderCache).foreach(_.close()) val cache = newClassLoaderCache + s.configuration.provider.scalaProvider.loader match { + case null => // This can happen in scripted + case fullScalaLoader => + val jars = s.configuration.provider.scalaProvider.jars + val (library, rest) = jars.partition(_.getName == "scala-library.jar") + library.toList match { + case l @ lj :: Nil => + fullScalaLoader.getParent match { + case null => // This can happen for old launchers. + case libraryLoader => + cache.cachedCustomClassloader(l, () => new UncloseableURLLoader(l, libraryLoader)) + fullScalaLoader match { + case u: URLClassLoader + if u.getURLs + .filterNot(_ == lj.toURI.toURL) + .sameElements(rest.map(_.toURI.toURL)) => + cache.cachedCustomClassloader( + jars.toList, + () => new UncloseableURLLoader(jars, fullScalaLoader) + ) + case _ => + } + } + } + } s.put(BasicKeys.extendedClassLoaderCache, cache) .put(BasicKeys.classLoaderCache, new IncClassLoaderCache(cache)) }