diff options
author | Guillaume Martres <smarter@ubuntu.com> | 2018-07-27 22:29:51 +0200 |
---|---|---|
committer | Li Haoyi <haoyi.sg@gmail.com> | 2018-07-28 04:29:51 +0800 |
commit | fa6987b5bff17640985ce0ab4d07440ef1189c45 (patch) | |
tree | 15d84ea24d703172c2303f5368b0229325e061e7 | |
parent | 450679672077a5d96e572e2526680db7ae3a1dcb (diff) | |
download | mill-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.scala | 43 |
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, |