b+tree: state the bounds a node actually has - #155
Open
psiha wants to merge 1 commit into
Open
Conversation
This was referenced Sep 9, 2026
psiha
force-pushed
the
bt/2-lower-bound
branch
from
September 9, 2026 10:34
3166f3f to
89b0981
Compare
psiha
force-pushed
the
bt/3-node-bounds
branch
3 times, most recently
from
September 9, 2026 10:43
b47e72a to
0b01005
Compare
psiha
force-pushed
the
bt/2-lower-bound
branch
from
September 9, 2026 10:56
89b0981 to
ecbe4dc
Compare
psiha
force-pushed
the
bt/3-node-bounds
branch
2 times, most recently
from
September 9, 2026 11:13
5bec902 to
7681361
Compare
Two places took a bound on faith. The minimum fill is not an arbitrary half: 'min_values = ceil( max / 2 )' is exactly what makes '2 * min <= max + 1' true, which is what makes 'either a sibling can lend a value, or the two merge into one node' true - the property the whole underflow half rests on (handle_underflow, merge_right_into_left, append_and_free, and the bulk-fill partitions written as 'min_values * 2'). Raising the minimum past it does not merely make those suboptimal, it makes the 2-into-1 merge overflow the node silently, before it asserts. Now a static_assert, so a higher fill target has to bring its own merge shape. The intra-node lower_bound/upper_bound took the LEAF's capacity for both the bound asserted on num_vals and the compile-time choice between a linear scan and a binary search - while also being called on inner nodes. For a set the leaf is the larger of the two, so the assumption held and the dispatch was merely conservative. Neither survives a leaf that carries anything besides the key: the leaf then holds fewer entries than the inner node, the assumption becomes false for a full inner node - an assumption, not an assertion, so a release build miscompiles rather than trips - and the dispatch would drag the larger node onto the linear path, past the byte limit it was measured against. Capacity is now a parameter, taken from whichever node is being searched.
psiha
force-pushed
the
bt/2-lower-bound
branch
from
September 9, 2026 12:01
ecbe4dc to
4d6d91d
Compare
psiha
force-pushed
the
bt/3-node-bounds
branch
from
September 9, 2026 12:01
7681361 to
de2a93a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two places took a bound on faith.
The minimum fill is not an arbitrary half.
min_values = ceil( max / 2 )is exactly what makes2 * min <= max + 1true, which is what makes "either a sibling can lend a value, or the two merge into one node" true — the property the whole underflow half rests on (handle_underflow,merge_right_into_left,append_and_free, and the bulk-fill partitions written asmin_values * 2). Raising the minimum past it does not merely make those suboptimal: it makes the 2-into-1 merge overflow the node silently, before it asserts —move_chldrnboundscountandtgt_beginseparately and never their sum. Now astatic_assert, so a higher fill target has to bring its own merge shape rather than discover this at runtime.Intra-node search used the leaf's capacity while also running on inner nodes — for both the bound assumed on
num_valsand the compile-time choice between a linear scan and a binary search. For a set the leaf is the larger of the two, so the assumption held and the dispatch was merely conservative. Neither survives a leaf that carries anything besides the key: the leaf then holds fewer entries than the inner node, the assumption becomes false for a full inner node — an assumption, not an assertion, so a release build miscompiles rather than trips — and the dispatch would drag the larger node onto the linear path, past the byte limit that path was measured against.Capacity is now a parameter, taken from whichever node is actually being searched.
Stacked on #154.