Skip to content

perf(vm): lazily clone parent upvalues on Closure 🪺#156

Merged
timfennis merged 1 commit into
masterfrom
perf/lazy-closure-upvalues
May 24, 2026
Merged

perf(vm): lazily clone parent upvalues on Closure 🪺#156
timfennis merged 1 commit into
masterfrom
perf/lazy-closure-upvalues

Conversation

@timfennis
Copy link
Copy Markdown
Owner

Context

A code-review agent flagged OpCode::Closure at ndc_vm/src/vm.rs:514 as wasteful: every closure creation pre-cloned the entire parent frame's upvalues Vec, even when the new closure didn't reference any of them. The Vec was only there to outlive the frame borrow so that capture_upvalue (which needs &mut self) could be called.

Change

Walk CaptureSource entries directly and resolve each on demand:

  • Local(slot)self.capture_upvalue(...) as before
  • Upvalue(slot) → briefly re-borrow the frame and clone just that one parent upvalue Rc

The two borrows never overlap in time, so the borrow checker is happy without the intermediate Vec. Closures that only capture locals now do zero parent-upvalue work.

Performance

Bench Baseline New Speedup
Targeted (parent has 16 upvalues, 200k closures) 215.0 ± 12.3 ms 200.4 ± 8.8 ms 1.07×
closures.ndc (existing, parent has 0 upvalues) 72.0 ± 3.4 ms 70.2 ± 3.2 ms 1.03× (noise)
fibonacci.ndc (regression check, no closures) 70.0 ± 2.5 ms 70.5 ± 3.0 ms no change

The existing closures.ndc bench doesn't exercise the case this fix targets — the captured closures sit directly under the script root, so the parent's upvalues was empty and the old code's pre-clone was Vec::new(). The targeted ad-hoc bench (16 parent upvalues × 200k creations) shows the real ~7% win.

A nested_closures.ndc bench could be added to track this path going forward — happy to do that as a follow-up if useful.

🤖 Generated with Claude Code

Walk the closure's CaptureSource list directly instead of pre-cloning
the entire parent upvalues Vec. Closures only pay for the upvalues they
actually reference; ones that only capture locals pay nothing extra.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@timfennis timfennis merged commit 5c896d4 into master May 24, 2026
1 check passed
@timfennis timfennis deleted the perf/lazy-closure-upvalues branch May 24, 2026 14:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant