From 2607e4dc47f0129ad5f67d9597259e4c3ff9bbf5 Mon Sep 17 00:00:00 2001 From: He-Pin Date: Sat, 30 May 2026 23:28:38 +0800 Subject: [PATCH] perf: memoize import path resolution in CachedImporter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CachedImporter.read is cached but resolve() was not — it delegated straight to parent.resolve on every call. resolve() runs on every visitImport (before the evaluator's by-path Val cache is consulted) and each call stats candidate paths (docBase + every jpath) via os.isFile. Resolution is deterministic within a run, so memoizing it by (docBase, importName) turns repeated imports and re-evaluated import exprs into a HashMap lookup, eliminating redundant filesystem stats. Mirrors the existing read cache. Result (Scala Native, kube-prometheus, 25 interleaved runs, cooled): total_time min 152.4 -> 149.2 ms, mean 159.1 -> 156.3 ms (~1.7-2%); output byte-identical, 518/518 tests pass. --- sjsonnet/src/sjsonnet/Importer.scala | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/sjsonnet/src/sjsonnet/Importer.scala b/sjsonnet/src/sjsonnet/Importer.scala index e11eea6e..0ce90646 100644 --- a/sjsonnet/src/sjsonnet/Importer.scala +++ b/sjsonnet/src/sjsonnet/Importer.scala @@ -201,7 +201,16 @@ class CachedImporter(parent: Importer) extends Importer { val cache: mutable.HashMap[(Path, Boolean), ResolvedFile] = mutable.HashMap.empty[(Path, Boolean), ResolvedFile] - def resolve(docBase: Path, importName: String): Option[Path] = parent.resolve(docBase, importName) + // Memoize path resolution by (docBase, importName). resolve() runs on every visitImport — before + // the evaluator's by-path Val cache is consulted — and each call stats candidate paths + // (docBase + every jpath) via os.isFile. Resolution is deterministic within a run, so caching it + // turns repeated imports (and re-evaluated import exprs) into a HashMap lookup, eliminating + // redundant filesystem stats. Mirrors the existing read cache. + private val resolveCache: mutable.HashMap[(Path, String), Option[Path]] = + mutable.HashMap.empty[(Path, String), Option[Path]] + + def resolve(docBase: Path, importName: String): Option[Path] = + resolveCache.getOrElseUpdate((docBase, importName), parent.resolve(docBase, importName)) def read(path: Path, binaryData: Boolean): Option[ResolvedFile] = { val key = (path, binaryData)