Attempting a different algorithm for rbyd range removals

The previous attempt to make range removals more rigorous highlighted a
pretty significant design flaw: Every removals risks making the tree ~2x
taller.

In theory this is offset by the fact that removals, well, remove nodes,
shrinking the height of the tree, but this isn't reflected in the
underlying red-black-yellow structure. Blanket recoloring breaks the
red-black-yellow invariants.

This isn't the end of the world, we still rebalance during rbyd
compaction, but it would be nice if we had stronger guarantees about
the structure of rbyds before compaction. Especially since we rely on
tree balance to defend our O(n log n) traversal overhead.

---

This attempts to reimplement range removals with two separate passes for
diverged trunk.

The downside is range removals now need, well, two separate passes, even
if we don't actually end up with a diverged trunk. The upside is in
theory we can preserve the coloring information and related invariants.

I would describe this commit as "almost working" and "a mess". There
still seems to be some issues with null tags getting stuck in the tree
after diverged trunks.

On the plus side, our tests are certainly working...
This commit is contained in:
Christopher Haster
2024-03-10 15:20:08 -05:00
parent d93dce8db2
commit 0b6cf7e9a7
+262 -70
View File
@@ -2726,6 +2726,8 @@ static int lfsr_rbyd_appendattr(lfs_t *lfs, lfsr_rbyd_t *rbyd,
LFS_ASSERT(!lfsr_tag_isinternal(tag));
// bit 7 is reserved for future subtype extensions
LFS_ASSERT(!(tag & 0x80));
// you can't delete more than what's in the rbyd
LFS_ASSERT(delta >= -(lfsr_srid_t)rbyd->weight);
// ignore noops
if (!tag) {
@@ -2791,6 +2793,21 @@ static int lfsr_rbyd_appendattr(lfs_t *lfs, lfsr_rbyd_t *rbyd,
b_tag = a_tag;
}
}
// a_tag = lfs_max16(a_tag, 0x1);
// b_tag = lfs_max16(b_tag, 0x1);
// keep track of the diverged branch if we're removing a range
bool diverging = (a_rid != b_rid || a_tag != b_tag);
bool d_lower = false;
lfs_size_t d_branch = 0;
lfsr_tag_t d_tag;
lfs_size_t d_branch_;
lfsr_srid_t d_upper_rid;
again:;
// TODO move me?
lfsr_tag_t tag__ = 0;
bool diverged = false;
// keep track of bounds as we descend down the tree
//
@@ -2800,7 +2817,7 @@ static int lfsr_rbyd_appendattr(lfs_t *lfs, lfsr_rbyd_t *rbyd,
lfs_size_t a_branch = lfsr_rbyd_trunk(rbyd);
lfsr_srid_t a_lower_rid = 0;
lfsr_srid_t a_upper_rid = rbyd->weight;
lfsr_tag_t a_lower_tag = 0;
lfsr_tag_t a_lower_tag = 0x0000;
lfsr_tag_t a_upper_tag = 0xffff;
// diverged state in case we are removing a range from the tree
@@ -2816,13 +2833,8 @@ static int lfsr_rbyd_appendattr(lfs_t *lfs, lfsr_rbyd_t *rbyd,
lfsr_tag_t b_lower_tag = 0;
lfsr_tag_t b_upper_tag = 0;
// go ahead and update the rbyd's weight, if an error occurs our
// rbyd is no longer usable anyways
LFS_ASSERT(delta >= -(lfsr_srid_t)rbyd->weight);
rbyd->weight += delta;
// assume we'll update our trunk
rbyd->trunk = (rbyd->trunk & LFSR_RBYD_ISSHRUB) | rbyd->eoff;
// the new trunk starts here
lfs_size_t trunk = (rbyd->trunk & LFSR_RBYD_ISSHRUB) | rbyd->eoff;
// no trunk yet?
if (!a_branch) {
@@ -2855,7 +2867,7 @@ static int lfsr_rbyd_appendattr(lfs_t *lfs, lfsr_rbyd_t *rbyd,
lfs_size_t branch_ = a_branch + d;
// do bounds want to take different paths? begin diverging
if (!lfsr_tag_hasdiverged(a_tag)
if (!diverged
&& lfsr_tag_follow2(alt, weight,
p_alts[0], p_weights[0],
a_lower_rid, a_upper_rid,
@@ -2864,6 +2876,8 @@ static int lfsr_rbyd_appendattr(lfs_t *lfs, lfsr_rbyd_t *rbyd,
p_alts[0], p_weights[0],
a_lower_rid, a_upper_rid,
b_rid, b_tag)) {
LFS_ASSERT(diverging);
// take care of any lingering red alts before diverging
if (lfsr_tag_isred(p_alts[0])) {
alt = p_alts[0] & ~LFSR_TAG_R;
@@ -2871,31 +2885,35 @@ static int lfsr_rbyd_appendattr(lfs_t *lfs, lfsr_rbyd_t *rbyd,
jump = p_jumps[0];
branch_ = a_branch;
lfsr_rbyd_p_pop(p_alts, p_weights, p_jumps);
// begin diverging
} else if (!d_lower) {
printf("diverged upper: %#x\n", a_branch);
// TODO need this?
alt &= ~LFSR_TAG_R;
d_branch = rbyd->eoff; // TODO need this?
diverged = true;
// TODO too many swaps?
lfs_swap16(&a_tag, &b_tag);
lfs_sswap32(&a_rid, &b_rid);
// connect diverged branch
} else {
a_tag |= LFSR_TAG_DIVERGED | LFSR_TAG_DIVERGEDLOWER;
b_tag |= LFSR_TAG_DIVERGED | LFSR_TAG_DIVERGEDUPPER;
b_branch = a_branch;
b_lower_rid = a_lower_rid;
b_upper_rid = a_upper_rid;
b_lower_tag = a_lower_tag;
b_upper_tag = a_upper_tag;
}
}
printf("diverged lower: %#x\n", a_branch);
// TODO need this?
alt &= ~LFSR_TAG_R;
// if (lfsr_tag_follow2(alt, weight,
// p_alts[0], p_weights[0],
// a_lower_rid, a_upper_rid,
// a_rid, a_tag)) {
// branch_ = d_branch;
// } else {
// jump = d_branch;
// }
// if we diverged, paint alts black, this isn't perfect but
// otherwise we run into recoloring issues
if (lfsr_tag_hasdiverged(a_tag)) {
alt &= ~LFSR_TAG_R;
// prune diverged?
if (lfsr_tag_isdivergedupper(a_tag)
^ lfsr_tag_isgt(alt)
^ lfsr_tag_follow2(
alt, weight,
p_alts[0], p_weights[0],
a_lower_rid, a_upper_rid,
a_rid, a_tag)) {
if (lfsr_tag_follow2(
alt, weight,
p_alts[0], p_weights[0],
@@ -2907,6 +2925,43 @@ static int lfsr_rbyd_appendattr(lfs_t *lfs, lfsr_rbyd_t *rbyd,
a_lower_rid, a_upper_rid);
lfs_swap32(&jump, &branch_);
}
printf("stitching: %#x %d-%d %#x\n",
LFSR_TAG_ALT(
LFSR_TAG_GT,
LFSR_TAG_B,
d_tag),
//alt,
//LFSR_TAG_ALT(LFSR_TAG_GT, LFSR_TAG_B, b_tag),
a_upper_rid, d_upper_rid,
d_branch);
// TODO ??? special handling for null?
// if (lfsr_tag_key(d_tag)) {
err = lfsr_rbyd_p_push(lfs, rbyd,
p_alts, p_weights, p_jumps,
LFSR_TAG_ALT(
LFSR_TAG_GT,
LFSR_TAG_B,
d_tag),
//lfs_smax32(weight + delta, 0),
a_upper_rid - d_upper_rid,
// LFSR_TAG_ALT(
// LFSR_TAG_LE,
// LFSR_TAG_B,
// // TODO this better?
// (lfsr_tag_key(a_tag))
// ? a_tag-1
// : 0),
// a_lower_rid,
d_branch);
if (err) {
return err;
}
// }
diverged = true;
lfsr_tag_trim(
alt, weight,
&a_lower_rid, &a_upper_rid,
@@ -2914,10 +2969,95 @@ static int lfsr_rbyd_appendattr(lfs_t *lfs, lfsr_rbyd_t *rbyd,
graft = a_branch;
a_branch = branch_;
goto next;
continue;
}
}
// // follow the upper path first
// // TODO too many swaps?
// lfs_swap16(&a_tag, &b_tag);
// lfs_sswap32(&a_rid, &b_rid);
// a_tag |= LFSR_TAG_DIVERGED | LFSR_TAG_DIVERGEDLOWER;
// b_tag |= LFSR_TAG_DIVERGED | LFSR_TAG_DIVERGEDUPPER;
// b_branch = a_branch;
// b_lower_rid = a_lower_rid;
// b_upper_rid = a_upper_rid;
// b_lower_tag = a_lower_tag;
// b_upper_tag = a_upper_tag;
// TODO rm me
if (diverged) {
alt &= ~LFSR_TAG_R;
}
// prune diverged?
// TODO can we merge with yellow prune?
if ((diverging && !d_lower && !diverged)
|| (diverged
&& !d_lower
^ lfsr_tag_isgt(alt)
^ lfsr_tag_follow2(
alt, weight,
p_alts[0], p_weights[0],
a_lower_rid, a_upper_rid,
a_rid, a_tag))) {
if (lfsr_tag_follow2(
alt, weight,
p_alts[0], p_weights[0],
a_lower_rid, a_upper_rid,
a_rid, a_tag)) {
lfsr_tag_flip2(
&alt, &weight,
p_alts[0], p_weights[0],
a_lower_rid, a_upper_rid);
lfs_swap32(&jump, &branch_);
}
lfsr_tag_trim(
alt, weight,
&a_lower_rid, &a_upper_rid,
&a_lower_tag, &a_upper_tag);
graft = a_branch;
a_branch = branch_;
continue;
}
// // if we diverged, paint alts black, this isn't perfect but
// // otherwise we run into recoloring issues
// if (lfsr_tag_hasdiverged(a_tag)) {
// alt &= ~LFSR_TAG_R;
//
// // prune diverged?
// if (lfsr_tag_isdivergedupper(a_tag)
// ^ lfsr_tag_isgt(alt)
// ^ lfsr_tag_follow2(
// alt, weight,
// p_alts[0], p_weights[0],
// a_lower_rid, a_upper_rid,
// a_rid, a_tag)) {
// if (lfsr_tag_follow2(
// alt, weight,
// p_alts[0], p_weights[0],
// a_lower_rid, a_upper_rid,
// a_rid, a_tag)) {
// lfsr_tag_flip2(
// &alt, &weight,
// p_alts[0], p_weights[0],
// a_lower_rid, a_upper_rid);
// lfs_swap32(&jump, &branch_);
// }
// lfsr_tag_trim(
// alt, weight,
// &a_lower_rid, &a_upper_rid,
// &a_lower_tag, &a_upper_tag);
//
// graft = a_branch;
// a_branch = branch_;
// goto next;
// }
// }
// prune?
// <b >b
// .-'| .-'|
@@ -3063,13 +3203,13 @@ static int lfsr_rbyd_appendattr(lfs_t *lfs, lfsr_rbyd_t *rbyd,
return err;
}
// if we diverged, stitch our paths together with alternating
// red alts, this gives us an optimal ternary tree if we
// started with a binary tree, but the above recoloring makes
// this not optimal
if (lfsr_tag_hasdiverged(a_tag) && lfsr_tag_isblack(p_alts[2])) {
lfsr_rbyd_p_red(p_alts, p_weights, p_jumps);
}
// // if we diverged, stitch our paths together with alternating
// // red alts, this gives us an optimal ternary tree if we
// // started with a binary tree, but the above recoloring makes
// // this not optimal
// if (lfsr_tag_hasdiverged(a_tag) && lfsr_tag_isblack(p_alts[2])) {
// lfsr_rbyd_p_red(p_alts, p_weights, p_jumps);
// }
// continue to next alt
graft = a_branch;
@@ -3079,49 +3219,98 @@ static int lfsr_rbyd_appendattr(lfs_t *lfs, lfsr_rbyd_t *rbyd,
} else {
// update the found tag/rid, marking as done while preserving
// any diverged state
a_tag = LFSR_TAG_DIVERGEDDONE
| lfsr_tag_mode(a_tag)
| alt;
tag__ = lfsr_tag_mode(a_tag) | alt;
// done?
if (!lfsr_tag_hasdiverged(a_tag)
|| lfsr_tag_isdivergeddone(b_tag)) {
break;
}
// // done?
// if (!lfsr_tag_hasdiverged(a_tag)
// || lfsr_tag_isdivergeddone(b_tag)) {
// break;
// }
break;
}
next:;
// switch to the other path if we have diverged
if (lfsr_tag_hasdiverged(a_tag)) {
lfs_swap16(&a_tag, &b_tag);
lfs_sswap32(&a_rid, &b_rid);
lfs_swap32(&a_branch, &b_branch);
lfs_sswap32(&a_lower_rid, &b_lower_rid);
lfs_sswap32(&a_upper_rid, &b_upper_rid);
lfs_swap16(&a_lower_tag, &b_lower_tag);
lfs_swap16(&a_upper_tag, &b_upper_tag);
}
// next:;
// // switch to the other path if we have diverged
// if (lfsr_tag_hasdiverged(a_tag)) {
// lfs_swap16(&a_tag, &b_tag);
// lfs_sswap32(&a_rid, &b_rid);
// lfs_swap32(&a_branch, &b_branch);
// lfs_sswap32(&a_lower_rid, &b_lower_rid);
// lfs_sswap32(&a_upper_rid, &b_upper_rid);
// lfs_swap16(&a_lower_tag, &b_lower_tag);
// lfs_swap16(&a_upper_tag, &b_upper_tag);
// }
}
// the last alt should always end up black
LFS_ASSERT(lfsr_tag_isblack(p_alts[0]));
// if we diverged, merge the bounds
LFS_ASSERT(lfsr_tag_isdivergeddone(a_tag));
LFS_ASSERT(!lfsr_tag_hasdiverged(a_tag)
|| lfsr_tag_isdivergeddone(b_tag));
if (lfsr_tag_hasdiverged(a_tag)) {
if (lfsr_tag_isdivergedlower(a_tag)) {
// finished on lower path
a_tag = b_tag;
a_branch = b_branch;
a_upper_rid = b_upper_rid;
} else {
// finished on upper path
a_lower_rid = b_lower_rid;
// // if we diverged, merge the bounds
// LFS_ASSERT(lfsr_tag_isdivergeddone(a_tag));
// LFS_ASSERT(!lfsr_tag_hasdiverged(a_tag)
// || lfsr_tag_isdivergeddone(b_tag));
// if (lfsr_tag_hasdiverged(a_tag)) {
// if (lfsr_tag_isdivergedlower(a_tag)) {
// // finished on lower path
// a_tag = b_tag;
// a_branch = b_branch;
// a_upper_rid = b_upper_rid;
// } else {
// // finished on upper path
// a_lower_rid = b_lower_rid;
// }
// }
// need a second diverging trunk?
if (diverging && !d_lower) {
// no diverging branch? guess we only need one trunk then
if (!diverged) {
diverging = false;
// TODO too many swaps
lfs_swap16(&a_tag, &b_tag);
lfs_sswap32(&a_rid, &b_rid);
goto again;
}
// flush any pending alts
err = lfsr_rbyd_p_flush(lfs, rbyd,
p_alts, p_weights, p_jumps, 3);
if (err) {
return err;
}
// terminate diverged branch with an unreachable tag
err = lfsr_rbyd_appendattr_(lfs, rbyd,
(lfsr_rbyd_isshrub(rbyd) ? LFSR_TAG_SHRUB : 0)
| LFSR_TAG_NULL,
0,
LFSR_DATA_NULL());
if (err) {
return err;
}
// save the found lower rid/tag
d_tag = tag__;
d_branch_ = a_branch;
d_upper_rid = a_upper_rid;
lfs_swap16(&a_tag, &b_tag);
lfs_sswap32(&a_rid, &b_rid);
d_lower = true;
printf("found dtag: %#x: %#x\n", d_branch_, d_tag);
goto again;
}
// use the diverged lower rid for leaf weight calculation
a_tag = tag__;
if (diverged) {
a_tag = d_tag;
a_branch = d_branch_;
a_upper_rid = d_upper_rid;
}
printf("done: %#x %d %d\n", a_tag, a_lower_rid, a_upper_rid);
// split leaf nodes?
//
// note we bias the weights here so that lfsr_rbyd_lookupnext
@@ -3231,6 +3420,9 @@ leaf:;
return err;
}
// update the trunk and weight
rbyd->trunk = trunk;
rbyd->weight += delta;
return 0;
}