From e64dfdbc20bba13bd5706ef21b7345b3e7db0637 Mon Sep 17 00:00:00 2001 From: He-Pin Date: Sun, 7 Jun 2026 04:14:49 +0800 Subject: [PATCH 1/2] perf: fuse chained std.map calls into ComposedMappedArr MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Motivation: When `std.map(f, std.map(g, arr))` is evaluated, two MappedArr views are nested. Each element access traverses two cache layers and two indirection levels. Common in Jsonnet when splitting logic across multiple map calls. Modification: In `Arr.mapped()`, detect when the source is already a `MappedArr` with live state and create a `ComposedMappedArr` that applies both functions in sequence from the original source, eliminating one cache layer and one level of indirection per element access. Result: Native: lazy_array_sparse_indexing 20.5 → 19.7ms (-3.9%) Existing test coverage: lazy_array_views.jsonnet tests chained maps. --- sjsonnet/src/sjsonnet/Val.scala | 38 ++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/sjsonnet/src/sjsonnet/Val.scala b/sjsonnet/src/sjsonnet/Val.scala index fdd0b9dc..3c6ea969 100644 --- a/sjsonnet/src/sjsonnet/Val.scala +++ b/sjsonnet/src/sjsonnet/Val.scala @@ -1230,8 +1230,8 @@ object Val { */ private final class MappedArr( pos0: Position, - private var source: Arr, - private var func: Func, + private[Val] var source: Arr, + private[Val] var func: Func, private var callPos: Position, private var ev: EvalScope) extends LazyViewArr(pos0, source.length) { @@ -1247,6 +1247,33 @@ object Val { } } + /** + * Fused view for std.map(f, std.map(g, arr)). Applies both functions in sequence without an + * intermediate MappedArr cache layer, eliminating one allocation + indirection per element. + */ + private final class ComposedMappedArr( + pos0: Position, + private var source: Arr, + private var outerFunc: Func, + private var innerFunc: Func, + private var callPos: Position, + private var ev: EvalScope) + extends LazyViewArr(pos0, source.length) { + + protected def computeAt(index: Int): Val = { + val inner = innerFunc.apply1(source.eval(index), callPos)(ev, TailstrictModeDisabled) + outerFunc.apply1(inner, callPos)(ev, TailstrictModeDisabled) + } + + override protected def releaseCapturedState(): Unit = { + source = null + outerFunc = null + innerFunc = null + callPos = null + ev = null + } + } + /** * Lazy view for std.mapWithIndex(func, arr). * @@ -1616,7 +1643,12 @@ object Val { i += 1 } Arr(pos, result) - } else new MappedArr(pos, source, func, callPos, ev) + } else + source match { + case inner: MappedArr if inner.source != null => + new ComposedMappedArr(pos, inner.source, func, inner.func, callPos, ev) + case _ => new MappedArr(pos, source, func, callPos, ev) + } def mappedWithIndex( pos: Position, From 225dfbf0267cf5a4007c6916c076f2ca00144d35 Mon Sep 17 00:00:00 2001 From: He-Pin Date: Sun, 7 Jun 2026 06:52:05 +0800 Subject: [PATCH 2/2] fix: use correct callPos for inner function in ComposedMappedArr Motivation: ComposedMappedArr was using the outer map's callPos for both inner and outer function calls, causing error stack frames to point to the wrong source position when the inner function fails. Modification: - Store separate outerCallPos and innerCallPos - Make MappedArr.callPos accessible as private[Val] for extraction - Add comprehensive fused map chain tests (full materialization, repeated indexed access, reverse, foldl) Result: Correct error positions when inner function of a fused map chain throws. --- sjsonnet/src/sjsonnet/Val.scala | 22 ++++++++++++++----- .../new_test_suite/lazy_array_views.jsonnet | 7 ++++++ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/sjsonnet/src/sjsonnet/Val.scala b/sjsonnet/src/sjsonnet/Val.scala index 3c6ea969..56fbec1e 100644 --- a/sjsonnet/src/sjsonnet/Val.scala +++ b/sjsonnet/src/sjsonnet/Val.scala @@ -1232,7 +1232,7 @@ object Val { pos0: Position, private[Val] var source: Arr, private[Val] var func: Func, - private var callPos: Position, + private[Val] var callPos: Position, private var ev: EvalScope) extends LazyViewArr(pos0, source.length) { @@ -1256,20 +1256,22 @@ object Val { private var source: Arr, private var outerFunc: Func, private var innerFunc: Func, - private var callPos: Position, + private var outerCallPos: Position, + private var innerCallPos: Position, private var ev: EvalScope) extends LazyViewArr(pos0, source.length) { protected def computeAt(index: Int): Val = { - val inner = innerFunc.apply1(source.eval(index), callPos)(ev, TailstrictModeDisabled) - outerFunc.apply1(inner, callPos)(ev, TailstrictModeDisabled) + val inner = innerFunc.apply1(source.eval(index), innerCallPos)(ev, TailstrictModeDisabled) + outerFunc.apply1(inner, outerCallPos)(ev, TailstrictModeDisabled) } override protected def releaseCapturedState(): Unit = { source = null outerFunc = null innerFunc = null - callPos = null + outerCallPos = null + innerCallPos = null ev = null } } @@ -1646,7 +1648,15 @@ object Val { } else source match { case inner: MappedArr if inner.source != null => - new ComposedMappedArr(pos, inner.source, func, inner.func, callPos, ev) + new ComposedMappedArr( + pos, + inner.source, + func, + inner.func, + callPos, + inner.callPos, + ev + ) case _ => new MappedArr(pos, source, func, callPos, ev) } diff --git a/sjsonnet/test/resources/new_test_suite/lazy_array_views.jsonnet b/sjsonnet/test/resources/new_test_suite/lazy_array_views.jsonnet index 3bd1a220..73f99dc8 100644 --- a/sjsonnet/test/resources/new_test_suite/lazy_array_views.jsonnet +++ b/sjsonnet/test/resources/new_test_suite/lazy_array_views.jsonnet @@ -8,10 +8,17 @@ local made = std.makeArray(100000, function(i) if i == 0 then error 'forced make local chain = std.map(function(x) x + 1, std.map(function(x) x * 2, std.makeArray(50000, function(i) i))); local withIdx = std.mapWithIndex(function(i, x) i * 10 + x, std.range(1, 3)); +// ComposedMappedArr: fused map chain with full materialization, repeated access, reverse +local fused = std.map(function(x) x * x, std.map(function(x) x + 1, std.range(0, 4))); + std.assertEqual(mapped[1], 11) && std.assertEqual(indexed[2], 32) && std.assertEqual(made[99999], 100000) && std.assertEqual(chain[49999], 99999) && std.assertEqual(std.reverse(withIdx), [23, 12, 1]) && std.assertEqual(std.foldl(function(acc, x) acc + x, std.makeArray(1000, function(i) i), 0), 499500) && +std.assertEqual(fused, [1, 4, 9, 16, 25]) && +std.assertEqual(fused[2], 9) && +std.assertEqual(std.reverse(fused), [25, 16, 9, 4, 1]) && +std.assertEqual(std.foldl(function(a, b) a + b, fused, 0), 55) && true