mirror of https://github.com/sbt/sbt.git
Add fast path for parsing commands
It can easily take 2ms or more to parse a command depending on state's combined parser. There are some commands that sbt requires to work that we can handle in microseconds instead of milliseconds by special casing them. After this change, I saw the performance of https://github.com/eatkins/scala-build-watch-performance improve by a consistent 4-5ms in the 3 source file example which was a drop from 120ms to 115ms. While not necessarily earth shattering, this difference could theoretically be much worse in other projects that have a lot of plugins and custom tasks/commands. I think it's worth the modest maintenance cost.
This commit is contained in:
parent
48f086059f
commit
caccba7112
|
|
@ -23,6 +23,7 @@ import sbt.util.Logger
|
|||
|
||||
import scala.annotation.tailrec
|
||||
import scala.util.control.NonFatal
|
||||
import sbt.internal.FastTrackCommands
|
||||
|
||||
object MainLoop {
|
||||
|
||||
|
|
@ -201,7 +202,14 @@ object MainLoop {
|
|||
StandardMain.exchange.setState(progressState)
|
||||
StandardMain.exchange.setExec(Some(exec))
|
||||
StandardMain.exchange.unprompt(ConsoleUnpromptEvent(exec.source))
|
||||
val newState = Command.process(exec.commandLine, progressState)
|
||||
/*
|
||||
* FastTrackCommands.evaluate can be significantly faster than Command.process because
|
||||
* it avoids an expensive parsing step for internal commands that are easy to parse.
|
||||
* Dropping (FastTrackCommands.evaluate ... getOrElse) should be functionally identical
|
||||
* but slower.
|
||||
*/
|
||||
val newState = FastTrackCommands.evaluate(progressState, exec.commandLine) getOrElse
|
||||
Command.process(exec.commandLine, progressState)
|
||||
// Flush the terminal output after command evaluation to ensure that all output
|
||||
// is displayed in the thin client before we report the command status.
|
||||
val terminal = channelName.flatMap(exchange.channelForName(_).map(_.terminal))
|
||||
|
|
|
|||
|
|
@ -1297,7 +1297,7 @@ private[sbt] object ContinuousCommands {
|
|||
case _ => state
|
||||
}
|
||||
}
|
||||
private[this] val failWatchCommand = watchCommand(failWatch) { (channel, state) =>
|
||||
private[sbt] val failWatchCommand = watchCommand(failWatch) { (channel, state) =>
|
||||
state.fail
|
||||
}
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt
|
||||
package internal
|
||||
|
||||
import BasicCommandStrings._
|
||||
import BasicCommands._
|
||||
import BuiltinCommands.{ setTerminalCommand, shell, waitCmd }
|
||||
import ContinuousCommands._
|
||||
|
||||
import sbt.internal.util.complete.Parser
|
||||
|
||||
/** This is used to speed up command parsing. */
|
||||
private[sbt] object FastTrackCommands {
|
||||
private def fromCommand(
|
||||
cmd: String,
|
||||
command: Command,
|
||||
arguments: Boolean = true,
|
||||
): (State, String) => Option[State] =
|
||||
(s, c) =>
|
||||
Parser.parse(if (arguments) c else "", command.parser(s)) match {
|
||||
case Right(newState) => Some(newState())
|
||||
case l => None
|
||||
}
|
||||
private val commands = Map[String, (State, String) => Option[State]](
|
||||
FailureWall -> { case (s, c) => if (c == FailureWall) Some(s) else None },
|
||||
StashOnFailure -> fromCommand(StashOnFailure, stashOnFailure, arguments = false),
|
||||
PopOnFailure -> fromCommand(PopOnFailure, popOnFailure, arguments = false),
|
||||
Shell -> fromCommand(Shell, shell),
|
||||
SetTerminal -> fromCommand(SetTerminal, setTerminalCommand),
|
||||
failWatch -> fromCommand(failWatch, failWatchCommand),
|
||||
preWatch -> fromCommand(preWatch, preWatchCommand),
|
||||
postWatch -> fromCommand(postWatch, postWatchCommand),
|
||||
runWatch -> fromCommand(runWatch, runWatchCommand),
|
||||
stopWatch -> fromCommand(stopWatch, stopWatchCommand),
|
||||
waitWatch -> fromCommand(waitWatch, waitCmd),
|
||||
)
|
||||
private[sbt] def evaluate(state: State, cmd: String): Option[State] = {
|
||||
cmd.trim.split(" ") match {
|
||||
case Array(h, _*) =>
|
||||
commands.get(h) match {
|
||||
case Some(command) => command(state, cmd)
|
||||
case _ => None
|
||||
}
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue