Skip to content

Commit 69d8de9

Browse files
Optimize BSP initialization stuff (forwardport #4698) (#4724)
Report of #4698 in `main` --- This optimizes the `workspaceBuildTargets` BSP request. On my machine, this makes it go from around 15s to about 3s on the Mill repo. Which is time saved when opening or reloading Mill in one's IDE. Note that gains are more modest on smaller code bases, but still there. In more detail, two things are optimized: - the `MillBuildServer.groupList` method, whose first implementation (that I wrote) was too naive (don't know how the code it replaced at the time fared) - it reduces the number of calls to `Evaluator.evaluate` when the BSP server needs to evaluate tasks. It does so by really grouping them by evaluator, rather than by (evaluator, BuildTargetId). Seems the former grouping was intended, but the grouping was actually more loose. Lastly, not an optimization per se, but this makes the BSP server less verbose. It was printing some very large objects in particular, that tend to flood the output / BSP server log. These messages are now only printed if the env var `MILL_BSP_DEBUG` is `true`.
1 parent 045faec commit 69d8de9

File tree

3 files changed

+79
-39
lines changed

3 files changed

+79
-39
lines changed

bsp/worker/src/mill/bsp/worker/BspWorkerImpl.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ private class BspWorkerImpl() extends BspWorker {
2929
serverVersion = BuildInfo.millVersion,
3030
serverName = Constants.serverName,
3131
logStream = logStream,
32-
canReload = canReload
32+
canReload = canReload,
33+
debugMessages = Option(System.getenv("MILL_BSP_DEBUG")).contains("true")
3334
) with MillJvmBuildServer with MillJavaBuildServer with MillScalaBuildServer
3435

3536
val executor = Executors.newCachedThreadPool()

bsp/worker/src/mill/bsp/worker/MillBuildServer.scala

Lines changed: 71 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import mill.given
1717

1818
import java.io.PrintStream
1919
import java.util.concurrent.CompletableFuture
20+
import scala.collection.mutable
2021
import scala.concurrent.Promise
2122
import scala.jdk.CollectionConverters.*
2223
import scala.reflect.ClassTag
@@ -29,7 +30,8 @@ private class MillBuildServer(
2930
serverVersion: String,
3031
serverName: String,
3132
logStream: PrintStream,
32-
canReload: Boolean
33+
canReload: Boolean,
34+
debugMessages: Boolean
3335
) extends ExternalModule
3436
with BuildServer {
3537

@@ -55,12 +57,16 @@ private class MillBuildServer(
5557
if (statePromise.isCompleted) statePromise = Promise[State]() // replace the promise
5658
evaluatorsOpt.foreach { evaluators =>
5759
statePromise.success(
58-
new State(topLevelProjectRoot, evaluators, debug)
60+
new State(topLevelProjectRoot, evaluators, s => debug(s()))
5961
)
6062
}
6163
}
6264

63-
def debug(msg: String): Unit = logStream.println(msg)
65+
def print(msg: String): Unit =
66+
logStream.println(msg)
67+
def debug(msg: => String): Unit =
68+
if (debugMessages)
69+
logStream.println("[debug] " + msg)
6470

6571
def onConnectWithClient(buildClient: BuildClient): Unit = client = buildClient
6672

@@ -109,7 +115,7 @@ private class MillBuildServer(
109115
case d: JsonObject =>
110116
debug(s"extra data: ${d} of type ${d.getClass}")
111117
readVersion(d, "semanticdbVersion").foreach { version =>
112-
debug(
118+
print(
113119
s"Got client semanticdbVersion: ${version}. Enabling SemanticDB support."
114120
)
115121
clientWantsSemanticDb = true
@@ -126,27 +132,27 @@ private class MillBuildServer(
126132
}
127133

128134
override def onBuildInitialized(): Unit = {
129-
debug("Build initialized")
135+
print("Build initialized")
130136
}
131137

132138
override def buildShutdown(): CompletableFuture[Object] = {
133-
debug(s"Entered buildShutdown")
139+
print("Entered buildShutdown")
134140
shutdownRequested = true
135141
onSessionEnd match {
136142
case None =>
137143
case Some(onEnd) =>
138-
debug("Shutdown build...")
144+
print("Shutdown build...")
139145
onEnd(BspServerResult.Shutdown)
140146
}
141147
SemanticDbJavaModule.resetContext()
142148
CompletableFuture.completedFuture(null.asInstanceOf[Object])
143149
}
144150
override def onBuildExit(): Unit = {
145-
debug("Entered onBuildExit")
151+
print("Entered onBuildExit")
146152
onSessionEnd match {
147153
case None =>
148154
case Some(onEnd) =>
149-
debug("Exiting build...")
155+
print("Exiting build...")
150156
onEnd(BspServerResult.Shutdown)
151157
}
152158
SemanticDbJavaModule.resetContext()
@@ -204,7 +210,7 @@ private class MillBuildServer(
204210
onSessionEnd match {
205211
case None => "unsupportedWorkspaceReload".asInstanceOf[Object]
206212
case Some(onEnd) =>
207-
debug("Reloading workspace...")
213+
print("Reloading workspace...")
208214
onEnd(BspServerResult.ReloadWorkspace).asInstanceOf[Object]
209215
}
210216
}
@@ -405,7 +411,7 @@ private class MillBuildServer(
405411
(Task.Anon { () }, ev)
406412
val task = Task.Anon {
407413
Task.log.debug(
408-
"Ignoring invalid compile request for test module ${m.bspBuildTarget.displayName}"
414+
s"Ignoring invalid compile request for test module ${m.bspBuildTarget.displayName}"
409415
)
410416
}
411417
(task, ev)
@@ -669,38 +675,50 @@ private class MillBuildServer(
669675
}
670676

671677
// group by evaluator (different root module)
672-
val evaluated = groupList(tasksSeq.toSeq)(_._2)(_._1)
673-
.map { case ((ev, id), ts) =>
674-
val results = evaluate(ev, ts)
675-
val failures = results.transitiveResults.collect {
676-
case (_, res: ExecResult.Failing[_]) => res
678+
val groups0 = groupList(tasksSeq.toSeq)(_._2._1) {
679+
case (tasks, (_, id)) => (id, tasks)
680+
}
681+
682+
val evaluated = groups0.flatMap {
683+
case (ev, targetIdTasks) =>
684+
val results = evaluate(ev, targetIdTasks.map(_._2))
685+
val idByTasks = targetIdTasks.map { case (id, task) => (task: Task[_], id) }.toMap
686+
val failures = results.transitiveResults.toSeq.collect {
687+
case (task, res: ExecResult.Failing[_]) if idByTasks.contains(task) =>
688+
(idByTasks(task), res)
677689
}
678690

679-
def logError(errorMsg: String): Unit = {
691+
def logError(id: BuildTargetIdentifier, errorMsg: String): Unit = {
680692
val msg = s"Request '$prefix' failed for ${id.getUri}: ${errorMsg}"
681693
debug(msg)
682694
client.onBuildLogMessage(new LogMessageParams(MessageType.ERROR, msg))
683695
}
684696

685-
if (failures.nonEmpty) {
686-
logError(failures.mkString(", "))
697+
if (failures.nonEmpty)
698+
for ((id, failure) <- failures)
699+
logError(id, failure.toString)
700+
701+
val resultsById = targetIdTasks.flatMap {
702+
case (id, task) =>
703+
results.transitiveResults(task)
704+
.asSuccess
705+
.map(_.value.value.asInstanceOf[W])
706+
.map((id, _))
687707
}
688708

689-
// only the successful results
690-
val successes = results.values.map(_.value).asInstanceOf[Seq[W]]
691-
successes
692-
.flatMap { v =>
709+
resultsById.flatMap {
710+
case (id, values) =>
693711
try {
694-
Seq(f(ev, state, id, state.bspModulesById(id)._1, v))
712+
Seq(f(ev, state, id, state.bspModulesById(id)._1, values))
695713
} catch {
696714
case NonFatal(e) =>
697-
logError(e.toString)
715+
logError(id, e.toString)
698716
Seq()
699717
}
700-
}
701-
}
718+
}
719+
}
702720

703-
agg(evaluated.flatten.asJava, state)
721+
agg(evaluated.asJava, state)
704722
}
705723
}
706724

@@ -717,12 +735,12 @@ private class MillBuildServer(
717735
hint: String,
718736
checkInitialized: Boolean = true
719737
)(f: State => V): CompletableFuture[V] = {
720-
debug(s"Entered ${hint}")
738+
print(s"Entered ${hint}")
721739

722740
val start = System.currentTimeMillis()
723741
val prefix = hint.split(" ").head
724742
def took =
725-
debug(s"${prefix} took ${System.currentTimeMillis() - start} msec")
743+
print(s"${prefix} took ${System.currentTimeMillis() - start} msec")
726744

727745
val future = new CompletableFuture[V]()
728746
if (checkInitialized && !initialized) {
@@ -762,11 +780,11 @@ private class MillBuildServer(
762780
hint: String,
763781
checkInitialized: Boolean = true
764782
)(f: => V): CompletableFuture[V] = {
765-
debug(s"Entered ${hint}")
783+
print(s"Entered ${hint}")
766784
val start = System.currentTimeMillis()
767785
val prefix = hint.split(" ").head
768786
def took =
769-
debug(s"${prefix} took ${System.currentTimeMillis() - start} msec")
787+
print(s"${prefix} took ${System.currentTimeMillis() - start} msec")
770788

771789
val future = new CompletableFuture[V]()
772790

@@ -834,10 +852,27 @@ private object MillBuildServer {
834852
* the order of appearance of the keys from the input sequence
835853
*/
836854
private def groupList[A, K, B](seq: Seq[A])(key: A => K)(f: A => B): Seq[(K, Seq[B])] = {
837-
val keyIndices = seq.map(key).distinct.zipWithIndex.toMap
838-
seq.groupMap(key)(f)
839-
.toSeq
840-
.sortBy { case (k, _) => keyIndices(k) }
855+
val map = new mutable.HashMap[K, mutable.ListBuffer[B]]
856+
val list = new mutable.ListBuffer[(K, mutable.ListBuffer[B])]
857+
for (a <- seq) {
858+
val k = key(a)
859+
val b = f(a)
860+
val l = map.getOrElseUpdate(
861+
k, {
862+
val buf = mutable.ListBuffer[B]()
863+
list.append((k, buf))
864+
buf
865+
}
866+
)
867+
l.append(b)
868+
}
869+
list
870+
.iterator
871+
.map {
872+
case (k, l) =>
873+
(k, l.result())
874+
}
875+
.toList
841876
}
842877

843878
def jvmBuildTarget(d: JvmBuildTarget): bsp4j.JvmBuildTarget =

bsp/worker/src/mill/bsp/worker/State.scala

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ import mill.scalalib.bsp.BspModule
55
import mill.scalalib.internal.JavaModuleUtils
66
import mill.define.{Evaluator, Module}
77

8-
private class State(workspaceDir: os.Path, evaluators: Seq[Evaluator], debug: String => Unit) {
8+
private class State(
9+
workspaceDir: os.Path,
10+
evaluators: Seq[Evaluator],
11+
debug: (() => String) => Unit
12+
) {
913
lazy val bspModulesIdList: Seq[(BuildTargetIdentifier, (BspModule, Evaluator))] = {
1014
val modules: Seq[(Module, Seq[Module], Evaluator)] = evaluators
1115
.map(ev => (ev.rootModule, JavaModuleUtils.transitiveModules(ev.rootModule), ev))
@@ -24,7 +28,7 @@ private class State(workspaceDir: os.Path, evaluators: Seq[Evaluator], debug: St
2428
}
2529
lazy val bspModulesById: Map[BuildTargetIdentifier, (BspModule, Evaluator)] = {
2630
val map = bspModulesIdList.toMap
27-
debug(s"BspModules: ${map.view.mapValues(_._1.bspDisplayName).toMap}")
31+
debug(() => s"BspModules: ${map.view.mapValues(_._1.bspDisplayName).toMap}")
2832
map
2933
}
3034

0 commit comments

Comments
 (0)