summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGuillaume Martres <smarter@ubuntu.com>2018-07-27 22:29:51 +0200
committerLi Haoyi <haoyi.sg@gmail.com>2018-07-28 04:29:51 +0800
commitfa6987b5bff17640985ce0ab4d07440ef1189c45 (patch)
tree15d84ea24d703172c2303f5368b0229325e061e7
parent450679672077a5d96e572e2526680db7ae3a1dcb (diff)
downloadmill-fa6987b5bff17640985ce0ab4d07440ef1189c45.tar.gz
mill-fa6987b5bff17640985ce0ab4d07440ef1189c45.tar.bz2
mill-fa6987b5bff17640985ce0ab4d07440ef1189c45.zip
Make hot compilation 2x faster by properly reusing classloaders (#393)
So far, Mill was caching ScalaInstance which contains a classloader but this is not enough: Zinc creates its own classloader by combining the ScalaInstance classloader with the path to the compiler-bridge. Zinc takes care of caching this classloader in each instance of ScalaCompiler. We can take advantage of this by caching an instance of Compilers since it contains everything we want to cache (ScalaCompiler and ScalaInstance). This significantly reduces the amount of classloading that happens at each compilation step, as measured by running: export _JAVA_OPTIONS="-XX:+UnlockDiagnosticVMOptions -XX:+TraceClassLoading -XX:+TraceClassUnloading" mill -i foo.compile Then looking at the output of `out/mill-worker-1/logs` while adding a new line in a source file in `foo` and running `mill -i foo.compile` again. The speedup is going to depend on the project, but I measured a ~2x improvement when running on the Mill build `time(core.compile())` and `rm -rf out/core/compile/` in a loop until results stabilized. See also the results that were obtained when a very similar issue was fixed in sbt itself: https://github.com/sbt/sbt/pull/2754
-rw-r--r--scalalib/worker/src/mill/scalalib/worker/ScalaWorker.scala43
1 files changed, 24 insertions, 19 deletions
diff --git a/scalalib/worker/src/mill/scalalib/worker/ScalaWorker.scala b/scalalib/worker/src/mill/scalalib/worker/ScalaWorker.scala
index 3e12565e..76f185c5 100644
--- a/scalalib/worker/src/mill/scalalib/worker/ScalaWorker.scala
+++ b/scalalib/worker/src/mill/scalalib/worker/ScalaWorker.scala
@@ -25,11 +25,14 @@ case class MockedLookup(am: File => Optional[CompileAnalysis]) extends PerClassp
class ScalaWorker(ctx0: mill.util.Ctx,
compilerBridgeClasspath: Array[String]) extends mill.scalalib.ScalaWorkerApi{
- @volatile var scalaInstanceCache = Option.empty[(Long, ScalaInstance)]
-
- def compileZincBridge(scalaVersion: String,
- sourcesJar: Path,
- compilerJars: Array[File]) = {
+ @volatile var compilersCache = Option.empty[(Long, Compilers)]
+
+ /** Compile the bridge if it doesn't exist yet and return the output directory.
+ * TODO: Proper invalidation, see #389
+ */
+ def compileZincBridgeIfNeeded(scalaVersion: String,
+ sourcesJar: Path,
+ compilerJars: Array[File]): Path = {
val workingDir = ctx0.dest / scalaVersion
val compiledDest = workingDir / 'compiled
if (!exists(workingDir)) {
@@ -52,7 +55,6 @@ class ScalaWorker(ctx0: mill.util.Ctx,
.get
.invoke(null, argsArray)
}
-
compiledDest
}
@@ -86,11 +88,14 @@ class ScalaWorker(ctx0: mill.util.Ctx,
val compileClasspathFiles = compileClasspath.map(_.toIO).toArray
val compilerJars = compilerClasspath.toArray.map(_.toIO)
- val compilerBridge = compileZincBridge(scalaVersion, compilerBridgeSources, compilerJars)
+ val compilerBridge = compileZincBridgeIfNeeded(scalaVersion, compilerBridgeSources, compilerJars)
+
+ val ic = new sbt.internal.inc.IncrementalCompilerImpl()
- val scalaInstanceSig = compilerClasspath.map(p => p.toString().hashCode + p.mtime.toMillis).sum
- val scalaInstance = scalaInstanceCache match{
- case Some((k, v)) if k == scalaInstanceSig => v
+ val compilerBridgeSig = compilerBridge.mtime.toMillis
+ val compilersSig = compilerBridgeSig + compilerClasspath.map(p => p.toString().hashCode + p.mtime.toMillis).sum
+ val compilers = compilersCache match {
+ case Some((k, v)) if k == compilersSig => v
case _ =>
val scalaInstance = new ScalaInstance(
version = scalaVersion,
@@ -100,13 +105,18 @@ class ScalaWorker(ctx0: mill.util.Ctx,
allJars = compilerJars,
explicitActual = None
)
- scalaInstanceCache = Some((scalaInstanceSig, scalaInstance))
- scalaInstance
+ val compilers = ic.compilers(
+ scalaInstance,
+ ClasspathOptionsUtil.boot,
+ None,
+ ZincUtil.scalaCompiler(scalaInstance, compilerBridge.toIO)
+ )
+ compilersCache = Some((compilersSig, compilers))
+ compilers
}
mkdir(ctx.dest)
- val ic = new sbt.internal.inc.IncrementalCompilerImpl()
val logger = {
val consoleAppender = MainAppender.defaultScreen(ConsoleOut.printStreamOut(
@@ -150,12 +160,7 @@ class ScalaWorker(ctx0: mill.util.Ctx,
maxErrors = 10,
sourcePositionMappers = Array(),
order = CompileOrder.Mixed,
- compilers = ic.compilers(
- scalaInstance,
- ClasspathOptionsUtil.boot,
- None,
- ZincUtil.scalaCompiler(scalaInstance, compilerBridge.toIO)
- ),
+ compilers = compilers,
setup = ic.setup(
lookup,
skip = false,