Status (rescoped 2026-08-08, updated 2026-08-08)
Unblocked — #665's rank1 micro-benchmark merged. benches/bp_rank_micro.rs now covers BalancedParens::rank1 across flat/deep/mixed tree shapes at 1K-1M opens, benchmark-only (no src/ changes). Pre-#596 baseline (10,000 rank1() queries/batch, µs):
| Platform |
1K |
10K |
100K |
1M |
| ARM (M4 Pro) |
~11.5 |
~11.6 |
~11.9 |
~12.8 |
| x86_64 (Zen 4 7950X) |
~26.7 |
~24.4 |
~24.7 |
~27.9 |
Shape (flat/deep/mixed) is neutral on both platforms — expected, since the current rank_l2 decode is a pure shift-and-mask independent of bit content; size drives the difference, with 1M falling out of cache on both architectures. This is the number any #596 implementation attempt should diff against on both rank1 (via this bench) and select1 (via the existing benches/bp_select_micro.rs, from #64) — both consumers still apply, this issue is otherwise unchanged from the assessment below.
Summary
rank_l2: Vec<u64> in BalancedParens costs 64 bits per 512-bit block =
12.5% of the BP bitmap — twice what #64's Step B saved. Poppy's actual
trade is to omit the per-word level and popcount up to 7 words inside a basic
block, which is exactly one 64-byte cache line and one the query must load
anyway for the partial word.
Why now
Interacts with #64's Step B, but not the way this issue originally stated.
The claim was that once select drove off rank_l1, rank_l2 would be left
with rank1 as its only consumer. That is wrong as #64 was implemented:
WithCsPoppy::select1 reads rank_l2 too, unpacking its 9-bit per-word
offsets (step 3 of its query) to land on the exact word without popcounting.
rank_l2 now has two consumers, not one.
That makes this issue more expensive than "delete an array", not less.
Dropping rank_l2 forces select1 back to popcounting up to 7 words inside
the block — reintroducing exactly the word scanning Step B removed, and most
likely widening the ARM select1 regression #64 measured (+6-14% micro, +7.4%
end-to-end on M4 Pro). Budget for changing both query paths, and re-run
bp_select_micro alongside the rank-side measurement rather than treating this
as rank-only work.
BP rank1 is very hot (cursor navigation), so the rank side remains a genuine
space/time trade needing end-to-end measurement, not a free reduction. Should
follow #64 so the select path isn't confounded with this change.
Deferred from
#64 (docs/plan/cspoppy.md §6, F1) — not scheduled, recorded so the reasoning
isn't lost.
Status (rescoped 2026-08-08, updated 2026-08-08)
Unblocked — #665's rank1 micro-benchmark merged.
benches/bp_rank_micro.rsnow coversBalancedParens::rank1acrossflat/deep/mixedtree shapes at 1K-1M opens, benchmark-only (nosrc/changes). Pre-#596 baseline (10,000rank1()queries/batch, µs):Shape (flat/deep/mixed) is neutral on both platforms — expected, since the current
rank_l2decode is a pure shift-and-mask independent of bit content; size drives the difference, with 1M falling out of cache on both architectures. This is the number any #596 implementation attempt should diff against on bothrank1(via this bench) andselect1(via the existingbenches/bp_select_micro.rs, from #64) — both consumers still apply, this issue is otherwise unchanged from the assessment below.Summary
rank_l2: Vec<u64>inBalancedParenscosts 64 bits per 512-bit block =12.5% of the BP bitmap — twice what #64's Step B saved. Poppy's actual
trade is to omit the per-word level and popcount up to 7 words inside a basic
block, which is exactly one 64-byte cache line and one the query must load
anyway for the partial word.
Why now
Interacts with #64's Step B, but not the way this issue originally stated.
The claim was that once select drove off
rank_l1,rank_l2would be leftwith
rank1as its only consumer. That is wrong as #64 was implemented:WithCsPoppy::select1readsrank_l2too, unpacking its 9-bit per-wordoffsets (step 3 of its query) to land on the exact word without popcounting.
rank_l2now has two consumers, not one.That makes this issue more expensive than "delete an array", not less.
Dropping
rank_l2forcesselect1back to popcounting up to 7 words insidethe block — reintroducing exactly the word scanning Step B removed, and most
likely widening the ARM select1 regression #64 measured (+6-14% micro, +7.4%
end-to-end on M4 Pro). Budget for changing both query paths, and re-run
bp_select_microalongside the rank-side measurement rather than treating thisas rank-only work.
BP
rank1is very hot (cursor navigation), so the rank side remains a genuinespace/time trade needing end-to-end measurement, not a free reduction. Should
follow #64 so the select path isn't confounded with this change.
Deferred from
#64 (
docs/plan/cspoppy.md§6, F1) — not scheduled, recorded so the reasoningisn't lost.