Commit 5c896d4
perf(vm): lazily clone parent upvalues on Closure 🪺 (#156)
## 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](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>1 parent b8d1c05 commit 5c896d4
1 file changed
Lines changed: 17 additions & 15 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
498 | 498 | | |
499 | 499 | | |
500 | 500 | | |
501 | | - | |
502 | | - | |
503 | | - | |
504 | | - | |
505 | | - | |
506 | | - | |
507 | | - | |
508 | | - | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
509 | 512 | | |
510 | | - | |
511 | | - | |
512 | | - | |
513 | | - | |
514 | | - | |
515 | | - | |
| 513 | + | |
| 514 | + | |
516 | 515 | | |
517 | 516 | | |
518 | 517 | | |
519 | 518 | | |
520 | 519 | | |
521 | 520 | | |
522 | | - | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
523 | 525 | | |
524 | 526 | | |
525 | 527 | | |
| |||
0 commit comments