You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Split out of #48 item 6, redirected by the allocation profile in PR #254.
What the profile actually found
#48's batch review proposed Value::Json(Box<serde_json::Value>) on a size argument: the workspace enables serde_json preserve_order, so Value carries an inline IndexMap and was predicted at ~88 B.
The measurement says the type is right and the mechanism is wrong:
Value is 72 B, not 88.
Boxing changes size_of::<Value>() 72 → 16 and the clone cost by exactly zero.
The predicted win was in the recursion frames. The profile measures 0.0% there.
kaish_kernel::kernel::Kernel::snapshot_exec_ctx (kernel.rs:2810)
<kaish_kernel::interpreter::scope::Scope as Clone>::clone (scope.rs:399)
<alloc::boxed::Box<T,A> as Clone>::clone ← Scope::last_result
<kaish_types::result::ExecResult as Clone>::clone (result.rs:162)
<core::option::Option<T> as Clone>::clone ← ExecResult.data
<kaish_types::value::Value as Clone>::clone (value.rs:18)
<serde_json::value::Value as Clone>::clone (Array arm)
<serde_json::map::Map<..> as Clone>::clone
indexmap::inner::Core::reserve_entries ← 728 B, ×1,440
Scope::last_result carries the previous ExecResult including .data, and every Scope::clone — 3 per command plus 1 per fork — deep-copies that JSON.
Two candidate fixes, and they are not the same size
(a) Value::Json(Arc<serde_json::Value>) — clone becomes a refcount bump everywhere a Value is cloned, not just here. Broader win, broader blast radius: types + kernel + tools + validator. Mutation sites need Arc::make_mut (clone-on-write), which is the right semantic since mutation is rare and cloning is hot. Note:serde needs its rc feature enabled to derive Serialize/Deserialize through an Arc.
(b) Arc the carrier instead of the payload — e.g. Scope::last_result: Option<Arc<ExecResult>>. Much narrower blast radius, and it kills the exact deep clone the profile fingered, since last_result is what Scope::clone copies. Does nothing for Value clones elsewhere.
Measure both before choosing. PR #254 landed a reusable allocation-profiling harness precisely so this kind of question is settled by numbers instead of argument. The scatter/gather workload is the one that moves.
Maintainer steer
Amy, on being offered Arc over Box: "I'm unopinionated, leaning Arc if it means less cloning." It does — that is the whole point of the redirect. But which thing gets the Arc is exactly what the harness should answer.
Watch for
Semantics: Arc makes the payload shared-immutable. Any site that pattern-matches Value::Json(v) and mutates v needs Arc::make_mut. Find them all — a missed one is a silent behavior change, not a compile error, if it goes through a clone first.
Split out of #48 item 6, redirected by the allocation profile in PR #254.
What the profile actually found
#48's batch review proposed
Value::Json(Box<serde_json::Value>)on a size argument: the workspace enables serde_jsonpreserve_order, soValuecarries an inlineIndexMapand was predicted at ~88 B.The measurement says the type is right and the mechanism is wrong:
Valueis 72 B, not 88.size_of::<Value>()72 → 16 and the clone cost by exactly zero.Value::Jsongenuinely is the glob output has extra blank lines in command substitution #1 allocation site in the scatter/gather workload — 53.6% of all allocations, 39.3% of all bytes — as a deep clone.The stack, verbatim from the profile:
Scope::last_resultcarries the previousExecResultincluding.data, and everyScope::clone— 3 per command plus 1 per fork — deep-copies that JSON.Two candidate fixes, and they are not the same size
(a)
Value::Json(Arc<serde_json::Value>)— clone becomes a refcount bump everywhere aValueis cloned, not just here. Broader win, broader blast radius: types + kernel + tools + validator. Mutation sites needArc::make_mut(clone-on-write), which is the right semantic since mutation is rare and cloning is hot. Note:serdeneeds itsrcfeature enabled to deriveSerialize/Deserializethrough anArc.(b) Arc the carrier instead of the payload — e.g.
Scope::last_result: Option<Arc<ExecResult>>. Much narrower blast radius, and it kills the exact deep clone the profile fingered, sincelast_resultis whatScope::clonecopies. Does nothing forValueclones elsewhere.Measure both before choosing. PR #254 landed a reusable allocation-profiling harness precisely so this kind of question is settled by numbers instead of argument. The scatter/gather workload is the one that moves.
Maintainer steer
Amy, on being offered
ArcoverBox: "I'm unopinionated, leaning Arc if it means less cloning." It does — that is the whole point of the redirect. But which thing gets theArcis exactly what the harness should answer.Watch for
Arcmakes the payload shared-immutable. Any site that pattern-matchesValue::Json(v)and mutatesvneedsArc::make_mut. Find them all — a missed one is a silent behavior change, not a compile error, if it goes through a clone first.preserve_orderis load-bearing (record-key ordering is relied on) — do not drop it as a shortcut. That was already rejected once in Interpreter memory/allocation optimization pass (tokio + ExecContext) #48.ExecResult.dataneeds to stayOption<Value>at all on thelast_resultpath.Size: one PR once the measurement picks the option. Breaking only if
Value's public shape changes in a way embedders see — check before assuming.