mirror of https://github.com/sbt/sbt.git
Merge pull request #8363 from eed3si9n/wip/hash
[2.x] perf: Cache content hash of binary files
This commit is contained in:
commit
83376f3cea
|
|
@ -358,6 +358,7 @@ lazy val utilCache = project
|
||||||
name := "Util Cache",
|
name := "Util Cache",
|
||||||
libraryDependencies ++=
|
libraryDependencies ++=
|
||||||
Seq(
|
Seq(
|
||||||
|
caffeine,
|
||||||
sjsonNewCore.value,
|
sjsonNewCore.value,
|
||||||
sjsonNewScalaJson.value,
|
sjsonNewScalaJson.value,
|
||||||
sjsonNewMurmurhash.value
|
sjsonNewMurmurhash.value
|
||||||
|
|
@ -365,6 +366,7 @@ lazy val utilCache = project
|
||||||
contrabandSettings,
|
contrabandSettings,
|
||||||
mimaSettings,
|
mimaSettings,
|
||||||
mimaBinaryIssueFilters ++= Seq(
|
mimaBinaryIssueFilters ++= Seq(
|
||||||
|
exclude[ReversedMissingMethodProblem]("sbt.util.CacheImplicits.*"),
|
||||||
),
|
),
|
||||||
Test / fork := true,
|
Test / fork := true,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,12 @@ object BasicKeys {
|
||||||
10000
|
10000
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val localDigestCacheByteSize = AttributeKey[Long](
|
||||||
|
"localDigestCacheByteSize",
|
||||||
|
"The maximum total size in the in-memory digest cache in bytes.",
|
||||||
|
10000
|
||||||
|
)
|
||||||
|
|
||||||
// Unlike other BasicKeys, this is not used directly as a setting key,
|
// Unlike other BasicKeys, this is not used directly as a setting key,
|
||||||
// and severLog / logLevel is used instead.
|
// and severLog / logLevel is used instead.
|
||||||
private[sbt] val serverLogLevel =
|
private[sbt] val serverLogLevel =
|
||||||
|
|
|
||||||
|
|
@ -274,6 +274,8 @@ object Def extends BuildSyntax with Init with InitializeImplicits:
|
||||||
|
|
||||||
// These are here, as opposed to RemoteCache, since we need them from TaskMacro etc
|
// These are here, as opposed to RemoteCache, since we need them from TaskMacro etc
|
||||||
private[sbt] val cacheEventLog: CacheEventLog = CacheEventLog()
|
private[sbt] val cacheEventLog: CacheEventLog = CacheEventLog()
|
||||||
|
private[sbt] val localDigestCacheByteSizeKey =
|
||||||
|
SettingKey[Long](BasicKeys.localDigestCacheByteSize)
|
||||||
@cacheLevel(include = Array.empty)
|
@cacheLevel(include = Array.empty)
|
||||||
val cacheConfiguration: Initialize[Task[BuildWideCacheConfiguration]] = Def.task {
|
val cacheConfiguration: Initialize[Task[BuildWideCacheConfiguration]] = Def.task {
|
||||||
val state = stateKey.value
|
val state = stateKey.value
|
||||||
|
|
@ -288,12 +290,14 @@ object Def extends BuildSyntax with Init with InitializeImplicits:
|
||||||
.getOrElse(
|
.getOrElse(
|
||||||
DiskActionCacheStore(state.baseDir.toPath.resolve("target/bootcache"), fileConverter)
|
DiskActionCacheStore(state.baseDir.toPath.resolve("target/bootcache"), fileConverter)
|
||||||
)
|
)
|
||||||
|
val cacheByteSize = localDigestCacheByteSizeKey.value
|
||||||
BuildWideCacheConfiguration(
|
BuildWideCacheConfiguration(
|
||||||
cacheStore,
|
cacheStore,
|
||||||
outputDirectory,
|
outputDirectory,
|
||||||
fileConverter,
|
fileConverter,
|
||||||
state.log,
|
state.log,
|
||||||
cacheEventLog
|
cacheEventLog,
|
||||||
|
cacheByteSize
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -440,6 +440,7 @@ object Keys {
|
||||||
val remoteCacheResolvers = settingKey[Seq[Resolver]]("Resolvers for remote cache.")
|
val remoteCacheResolvers = settingKey[Seq[Resolver]]("Resolvers for remote cache.")
|
||||||
val remoteCachePom = taskKey[HashedVirtualFileRef]("Generates a pom for publishing when publishing Maven-style.")
|
val remoteCachePom = taskKey[HashedVirtualFileRef]("Generates a pom for publishing when publishing Maven-style.")
|
||||||
val localCacheDirectory = settingKey[File]("Operating system specific cache directory.")
|
val localCacheDirectory = settingKey[File]("Operating system specific cache directory.")
|
||||||
|
val localDigestCacheByteSize = SettingKey[Long](BasicKeys.localDigestCacheByteSize).withRank(DSetting)
|
||||||
val usePipelining = settingKey[Boolean]("Use subproject pipelining for compilation.").withRank(BSetting)
|
val usePipelining = settingKey[Boolean]("Use subproject pipelining for compilation.").withRank(BSetting)
|
||||||
val exportPipelining = settingKey[Boolean]("Produce early output so downstream subprojects can do pipelining.").withRank(BSetting)
|
val exportPipelining = settingKey[Boolean]("Produce early output so downstream subprojects can do pipelining.").withRank(BSetting)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ import sbt.nio.FileStamp
|
||||||
import sbt.nio.Keys.{ inputFileStamps, outputFileStamps }
|
import sbt.nio.Keys.{ inputFileStamps, outputFileStamps }
|
||||||
import sbt.std.TaskExtra.*
|
import sbt.std.TaskExtra.*
|
||||||
import sbt.util.InterfaceUtil.toOption
|
import sbt.util.InterfaceUtil.toOption
|
||||||
import sbt.util.{ DiskActionCacheStore, Logger }
|
import sbt.util.{ CacheImplicits, DiskActionCacheStore, Logger }
|
||||||
import sbt.util.CacheImplicits.given
|
import sbt.util.CacheImplicits.given
|
||||||
import sjsonnew.JsonFormat
|
import sjsonnew.JsonFormat
|
||||||
import xsbti.{ FileConverter, HashedVirtualFileRef, VirtualFileRef }
|
import xsbti.{ FileConverter, HashedVirtualFileRef, VirtualFileRef }
|
||||||
|
|
@ -78,6 +78,7 @@ object RemoteCache {
|
||||||
remoteCacheIdCandidates :== Nil,
|
remoteCacheIdCandidates :== Nil,
|
||||||
pushRemoteCacheTo :== None,
|
pushRemoteCacheTo :== None,
|
||||||
localCacheDirectory :== defaultCacheLocation,
|
localCacheDirectory :== defaultCacheLocation,
|
||||||
|
localDigestCacheByteSize :== CacheImplicits.defaultLocalDigestCacheByteSize,
|
||||||
pushRemoteCache / ivyPaths := {
|
pushRemoteCache / ivyPaths := {
|
||||||
val app = appConfiguration.value
|
val app = appConfiguration.value
|
||||||
val base = app.baseDirectory.getCanonicalFile
|
val base = app.baseDirectory.getCanonicalFile
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ object LintUnused {
|
||||||
evictionWarningOptions,
|
evictionWarningOptions,
|
||||||
initialize,
|
initialize,
|
||||||
lintUnusedKeysOnLoad,
|
lintUnusedKeysOnLoad,
|
||||||
|
localDigestCacheByteSize,
|
||||||
onLoad,
|
onLoad,
|
||||||
onLoadMessage,
|
onLoadMessage,
|
||||||
onUnload,
|
onUnload,
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import sbt.io.syntax.*
|
||||||
import sbt.io.IO
|
import sbt.io.IO
|
||||||
import sbt.nio.file.{ **, FileTreeView }
|
import sbt.nio.file.{ **, FileTreeView }
|
||||||
import sbt.nio.file.syntax.*
|
import sbt.nio.file.syntax.*
|
||||||
|
import sbt.util.CacheImplicits
|
||||||
import scala.reflect.ClassTag
|
import scala.reflect.ClassTag
|
||||||
import scala.annotation.{ meta, StaticAnnotation }
|
import scala.annotation.{ meta, StaticAnnotation }
|
||||||
import sjsonnew.{ HashWriter, JsonFormat }
|
import sjsonnew.{ HashWriter, JsonFormat }
|
||||||
|
|
@ -127,6 +128,7 @@ object ActionCache:
|
||||||
config: BuildWideCacheConfiguration,
|
config: BuildWideCacheConfiguration,
|
||||||
): Either[Throwable, ActionResult] =
|
): Either[Throwable, ActionResult] =
|
||||||
// val logger = config.logger
|
// val logger = config.logger
|
||||||
|
CacheImplicits.setCacheSize(config.localDigestCacheByteSize)
|
||||||
val (input, valuePath) = mkInput(key, codeContentHash, extraHash)
|
val (input, valuePath) = mkInput(key, codeContentHash, extraHash)
|
||||||
val getRequest =
|
val getRequest =
|
||||||
GetActionResultRequest(input, inlineStdout = false, inlineStderr = false, Vector(valuePath))
|
GetActionResultRequest(input, inlineStdout = false, inlineStderr = false, Vector(valuePath))
|
||||||
|
|
@ -210,7 +212,24 @@ class BuildWideCacheConfiguration(
|
||||||
val fileConverter: FileConverter,
|
val fileConverter: FileConverter,
|
||||||
val logger: Logger,
|
val logger: Logger,
|
||||||
val cacheEventLog: CacheEventLog,
|
val cacheEventLog: CacheEventLog,
|
||||||
|
val localDigestCacheByteSize: Long,
|
||||||
):
|
):
|
||||||
|
def this(
|
||||||
|
store: ActionCacheStore,
|
||||||
|
outputDirectory: Path,
|
||||||
|
fileConverter: FileConverter,
|
||||||
|
logger: Logger,
|
||||||
|
cacheEventLog: CacheEventLog
|
||||||
|
) =
|
||||||
|
this(
|
||||||
|
store,
|
||||||
|
outputDirectory,
|
||||||
|
fileConverter,
|
||||||
|
logger,
|
||||||
|
cacheEventLog,
|
||||||
|
CacheImplicits.defaultLocalDigestCacheByteSize
|
||||||
|
)
|
||||||
|
|
||||||
override def toString(): String =
|
override def toString(): String =
|
||||||
s"BuildWideCacheConfiguration(store = $store, outputDirectory = $outputDirectory)"
|
s"BuildWideCacheConfiguration(store = $store, outputDirectory = $outputDirectory)"
|
||||||
end BuildWideCacheConfiguration
|
end BuildWideCacheConfiguration
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,72 @@
|
||||||
|
|
||||||
package sbt.util
|
package sbt.util
|
||||||
|
|
||||||
|
import com.github.benmanes.caffeine.cache.{ Cache as CCache, Caffeine, Weigher }
|
||||||
|
import java.nio.file.{ Files, NoSuchFileException }
|
||||||
|
import java.nio.file.attribute.BasicFileAttributes
|
||||||
|
import java.util.concurrent.atomic.{ AtomicLong, AtomicReference }
|
||||||
import sjsonnew.BasicJsonProtocol
|
import sjsonnew.BasicJsonProtocol
|
||||||
|
import xsbti.{ HashedVirtualFileRef, PathBasedFile }
|
||||||
|
|
||||||
object CacheImplicits extends CacheImplicits
|
object CacheImplicits extends CacheImplicits:
|
||||||
trait CacheImplicits extends BasicCacheImplicits with BasicJsonProtocol
|
private[sbt] val defaultLocalDigestCacheByteSize = 1024L * 1024L
|
||||||
|
end CacheImplicits
|
||||||
|
|
||||||
|
trait CacheImplicits extends BasicCacheImplicits with BasicJsonProtocol:
|
||||||
|
private val localDigestCacheByteSize = AtomicLong(CacheImplicits.defaultLocalDigestCacheByteSize)
|
||||||
|
private val weigher: Weigher[String, (String, Long, Long)] = { case (k, (v1, _, _)) =>
|
||||||
|
k.size + v1.size + 16
|
||||||
|
}
|
||||||
|
|
||||||
|
private val stampCache: AtomicReference[CCache[String, (String, Long, Long)]] =
|
||||||
|
AtomicReference(
|
||||||
|
Caffeine
|
||||||
|
.newBuilder()
|
||||||
|
.maximumWeight(localDigestCacheByteSize.get())
|
||||||
|
.weigher(weigher)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
|
||||||
|
private[sbt] def setCacheSize(size: Long): Unit =
|
||||||
|
if localDigestCacheByteSize.get() == size then ()
|
||||||
|
else
|
||||||
|
localDigestCacheByteSize.set(size)
|
||||||
|
stampCache.get().invalidateAll()
|
||||||
|
stampCache.set(
|
||||||
|
Caffeine
|
||||||
|
.newBuilder()
|
||||||
|
.maximumWeight(localDigestCacheByteSize.get())
|
||||||
|
.weigher(weigher)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
|
||||||
|
private def getOrElseUpdate(ref: HashedVirtualFileRef, lastModified: Long, sizeBytes: Long)(
|
||||||
|
value: => String
|
||||||
|
) =
|
||||||
|
Option(stampCache.get().getIfPresent(ref.id())) match
|
||||||
|
case Some((v, mod, i)) if lastModified == mod && sizeBytes == i => v
|
||||||
|
case _ =>
|
||||||
|
val v = value
|
||||||
|
stampCache.get().put(ref.id(), (v, lastModified, sizeBytes))
|
||||||
|
v
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A string representation of HashedVirtualFileRef, delimited by `>`.
|
||||||
|
*/
|
||||||
|
override def hashedVirtualFileRefToStr(ref: HashedVirtualFileRef): String =
|
||||||
|
def fallback: String = super.hashedVirtualFileRefToStr(ref)
|
||||||
|
if ref.id().endsWith(".scala") || ref.id().endsWith(".java") then fallback
|
||||||
|
else
|
||||||
|
ref match
|
||||||
|
case pbf: PathBasedFile =>
|
||||||
|
val path = pbf.toPath
|
||||||
|
try
|
||||||
|
val attrs = Files.readAttributes(path, classOf[BasicFileAttributes])
|
||||||
|
if attrs.isDirectory then fallback
|
||||||
|
else
|
||||||
|
val lastModified = attrs.lastModifiedTime().toMillis()
|
||||||
|
val sizeBytes = attrs.size()
|
||||||
|
getOrElseUpdate(ref, lastModified, sizeBytes)(fallback)
|
||||||
|
catch case _: NoSuchFileException => fallback
|
||||||
|
case _ => fallback
|
||||||
|
end CacheImplicits
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue