Skip to content

Fix graft_child_maps and graft_masked_branches grafting nothing - #182

Merged
imlvts merged 2 commits into
Adam-Vandervorst:masterfrom
imlvts:fix/graft-nothing
Nov 5, 2026
Merged

imlvts merged 2 commits into
Adam-Vandervorst:masterfrom
imlvts:fix/graft-nothing

Conversation

@imlvts

@imlvts imlvts commented Nov 3, 2026

Copy link
Copy Markdown
Collaborator

Grafting nothing at a branch must neither create nor destroy a location: an existing branch is emptied and survives as a dangling path, an absent one stays absent, as after graft of an empty source. graft_child_maps' slow path left the old branch in place for an empty map or one without a root value, its fast path grafted an empty node (tripping a debug assertion) and created the focus, and graft_masked_branches created the focus when every masked branch was absent from the source.

@Adam-Vandervorst

Adam-Vandervorst commented Nov 4, 2026 •

Copy link
Copy Markdown
Owner

Can we get a specialized micro benchmark for them in the progress so we can compare fixes?

Grafting nothing at a branch must neither create nor destroy a location:
an existing branch is emptied and survives as a dangling path, an absent
one stays absent, as after graft of an empty source.  graft_child_maps'
slow path left the old branch in place for an empty map or one without a
root value, its fast path grafted an empty node (tripping a debug
assertion) and created the focus, and graft_masked_branches created the
focus when every masked branch was absent from the source.
@imlvts
imlvts force-pushed the fix/graft-nothing branch from 348a5dd to aac0433 Compare November 4, 2026 04:23
@imlvts

imlvts commented Nov 4, 2026 •

Copy link
Copy Markdown
Collaborator Author

Change from master to fix/graft-nothing (worktree: aac0433 + faster graft_child_maps), with the 95% confidence interval in brackets. Positive means slower.
30 alternating runs per version, pinned to one CPU core; intervals from resampling the per-run medians.
Bold: the interval excludes 0 and the change is at least 5%.

bench 1 2 3 8 30 100 200 at 200: master → fix at 200, committed aac0433
graft_masked_branches
src_full +1 [+1, +2] +4 [+4, +5] +1 [+1, +2] +1 [+0, +2] +2 [+1, +3] +2 [+1, +3] +3 [+2, +4] 2.38 µs → 2.45 µs +3%
src_half +0 [−0, +1] +6 [+5, +7] +2 [+1, +2] +2 [+2, +3] +6 [+5, +7] +8 [+7, +9] +11 [+10, +12] 1.85 µs → 2.05 µs +11%
src_half_remove_unset −2 [−4, +1] −8 [−9, −7] +0 [−3, +4] +2 [+2, +3] +0 [−1, +1] −1 [−2, +0] +0 [−0, +1] 1.04 µs → 1.04 µs −0%
src_dangling +0 [−0, +1] +2 [+2, +3] +0 [−0, +0] +1 [+0, +1] +2 [+1, +3] +1 [+0, +2] +4 [+3, +4] 1.77 µs → 1.84 µs +4%
src_none_masked +0 [−0, +1] +2 [+1, +2] +2 [+1, +2] +5 [+4, +6] +10 [+9, +11] +17 [+16, +18] +40 [+39, +41] 1.29 µs → 1.81 µs +40%
src_bare_value −0 [−1, +0] +1 [+0, +2] −4 [−5, −4] −1 [−2, −1] +7 [+6, +8] +15 [+14, +16] +37 [+36, +38] 1.32 µs → 1.81 µs +39%
missing_focus_src_full −0 [−1, +1] +0 [−1, +1] −3 [−4, −2] −1 [−2, +0] −0 [−1, +0] +2 [+1, +3] +1 [+0, +1] 3.59 µs → 3.62 µs +1%
missing_focus_src_none_masked −4 [−5, −4] −6 [−6, −5] −30 [−31, −30] −26 [−26, −25] −14 [−14, −13] −6 [−7, −5] −6 [−7, −6] 2.53 µs → 2.37 µs −7%
graft_child_maps
keep_full +12 [+10, +13] +9 [+8, +9] −5 [−6, −4] −22 [−23, −22] −37 [−37, −36] −49 [−50, −49] −64 [−64, −64] 12.88 µs → 4.60 µs −20%
keep_half_empty +11 [+10, +12] +24 [+24, +25] +1 [+0, +2] −2 [−2, −1] −12 [−12, −11] −26 [−27, −26] −54 [−54, −53] 7.11 µs → 3.29 µs +28%
remove_unset_full −2 [−4, −1] +1 [+0, +2] −6 [−7, −4] +5 [+1, +8] −6 [−7, −6] −4 [−5, −4] −2 [−3, −2] 1.42 µs → 1.39 µs +5%
remove_unset_half_empty −3 [−4, −2] −0 [−1, +1] −7 [−8, −6] +9 [+7, +11] −4 [−5, −3] −5 [−5, −4] −4 [−4, −3] 953 ns → 917 ns +2%

Without remove_unset, more than two maps were grafted one branch at a
time, each a descend, graft, set or remove value, and ascend.  The maps
are now gathered into one byte node and merged into the focus the way
graft_masked_branches merges a source node, which that merge is factored
out for.  An absent source node is now an empty byte node on the stack
rather than an allocated one.
@imlvts

imlvts commented Nov 4, 2026

Copy link
Copy Markdown
Collaborator Author

According to claude, the 40% perf loss src_none_masked and src_bare_value is inherent to the fix itself. on master, not doing that work is the bug.

@Adam-Vandervorst

Copy link
Copy Markdown
Owner

Hmm that's quite a swing for only serving the purpose of giving a faster zipper algebra -- I can't recall how much this specialization saved. Especially keep half empty, I guess. Do we have benchmarks on zipper algebra @marcin-rzeznicki ?

@imlvts

imlvts commented Nov 4, 2026 •

Copy link
Copy Markdown
Collaborator Author

keep_half_empty is slower on small maps but faster on bigger maps (vs master)

@imlvts
imlvts merged commit 9bfae5d into Adam-Vandervorst:master Nov 5, 2026
5 checks passed
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.

2 participants