From d966c4091757bfb978038c7060b073b42f6ed62a Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Tue, 17 Sep 2019 18:41:40 -0700 Subject: [PATCH] Preload a number of classes in the background I was looking into sbt start up time and in profiling was able to identify a number of classloading bottlenecks. To speed up initialization, we can preload those classes in the background. I saw average speedups of roughly .75 seconds after this change. Also, the `time` command would consistently report cpu system time very close to 400% and I have 4 cores on my laptop. With 1.3.0 it would be more like 350%. --- .../sbt/internal/XMainConfiguration.scala | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/main/src/main/scala/sbt/internal/XMainConfiguration.scala b/main/src/main/scala/sbt/internal/XMainConfiguration.scala index 572da1b39..a66e3a0dc 100644 --- a/main/src/main/scala/sbt/internal/XMainConfiguration.scala +++ b/main/src/main/scala/sbt/internal/XMainConfiguration.scala @@ -10,10 +10,43 @@ package sbt.internal import java.io.File import java.lang.reflect.InvocationTargetException import java.net.{ URL, URLClassLoader } +import java.util.concurrent.{ ExecutorService, Executors } import java.util.regex.Pattern +import sbt.plugins.{ CorePlugin, IvyPlugin, JvmPlugin } +import sbt.util.LogExchange import xsbti._ +private[internal] object ClassLoaderWarmup { + def warmup(): Unit = { + if (Runtime.getRuntime.availableProcessors > 1) { + val executorService: ExecutorService = + Executors.newFixedThreadPool(Runtime.getRuntime.availableProcessors - 1) + def submit[R](f: => R): Unit = { + executorService.submit(new Runnable { + override def run(): Unit = { f; () } + }) + () + } + + submit(LogExchange.context) + submit(Class.forName("sbt.internal.parser.SbtParserInit").getConstructor().newInstance()) + submit(CorePlugin.projectSettings) + submit(IvyPlugin.projectSettings) + submit(JvmPlugin.projectSettings) + submit(() => { + try { + val clazz = Class.forName("scala.reflect.runtime.package$") + clazz.getMethod("universe").invoke(clazz.getField("MODULE$").get(null)) + } catch { + case _: Exception => + } + executorService.shutdown() + }) + } + } +} + /** * Generates a new app configuration and invokes xMainImpl.run. For AppConfigurations generated * by recent launchers, it is unnecessary to modify the original configuration, but configurations @@ -42,7 +75,8 @@ private[sbt] class XMainConfiguration { val instance = clazz.getField("MODULE$").get(null) val runMethod = clazz.getMethod("run", classOf[xsbti.AppConfiguration]) try { - loader.loadClass("sbt.internal.parser.SbtParserInit").getConstructor().newInstance() + val clw = loader.loadClass("sbt.internal.ClassLoaderWarmup$") + clw.getMethod("warmup").invoke(clw.getField("MODULE$").get(null)) runMethod.invoke(instance, updatedConfiguration).asInstanceOf[xsbti.MainResult] } catch { case e: InvocationTargetException =>