From 233fc2c2129b04e0741b084f9be13b8ffe2cfbfe Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Wed, 3 Apr 2024 21:54:21 -0500 Subject: [PATCH] rbyd-rr: Attempting correct balance of the diverging node itself MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit So far, our color-balance preserving range removal algorithm is working great: - Common trunk? color-balance preserving ✓ - Lower-diverged trunk? color-balance preserving ✓ - Upper-diverged trunk? color-balance preserving ✓ The only hole in our algorithm is the color-balance of the diverging node itself. Up until now we've simply recolored the diverging alt black, as this avoids a large number of complicated corner cases. Unfortunately this has the consequence of potentially offsetting the balance of our tree by +-1: .-> b-> .---b-> h=2 -. .---b-> rm me | | | .-> => => b-b-b-> +- unbalanced :( | .-b-> | '-> | | | .-> | .-> | r-b-b-> '-b-> h=3 -' ^ diverging This attempts to preserve the coloring of the diverging alt, and preserve the color-balance, but we quickly run into the, uh, previously mentioned complicated corner cases... - First to note, we _can_ preserve red coloring on the gt path: .-> b-> .---b-> h=2 -. .---b-> rm me | | | .-> => => r-b-b-> +- balanced :) | .-b-> | '-> | | | .-> | .-> | r-b-b-> '-b-> h=2 -' ^ diverging But only if it isn't a part of a pending yellow split. If it _is_ a pending yellow split, the yellow split may try to reference the yellow node in the history, but this won't work because our history has been modified: .-> .-> h=2 -. .-----b-> .-----b-> | | .-> b-> | .---b-> | | .---b-> rm me => => | | +- unbalanced :( | | .-> r-b-b-b-> | | | .-b-> | '-> | | | | .-> '-b-> | y-r-b-b-> '-> h=3 -' ^ '-+-' diverging wants to have split - As for the le path, we can't even preserve the red coloring! For this to work we would need to somehow color a flipped alt red (so the "follow" edge is red, not the "not-follow"), but this isn't possible with our encoding scheme (and definitely not worth reserving a whole additional bit in every alt for): r-b-b-> .-b-> .-b-> h=3 -. | | '-> | '-> | '-> | | '-b-> => | .-> => | .-> +- unbalanced :( | '-> b-b-> .-b-b-> | '---b-> rm me | | '-> b---b-> h=2 -' ^ ^ diverging this wants to be red The reason we can preserve reds on the gt path but not the le path is because we write the le path first and stitch on the gt path. If instead you wrote the gt path first, this would be flipped: r-b-b-> .-b-> h=2 -. | | '-> | '-> | | '-b-> => => | .-> +- balanced :) | '-> r-b-b-> | '---b-> rm me | | '-> b-> '---b-> h=2 -' ^ diverging In theory, you could do _another_ pass over the tree to figure out which order is needed to preserve coloring. But this would be an even more complicated mess... Not to mention this wouldn't even completely solve the color-balance of the diverging alt because of yellow split issues... And we haven't even touched issues related to yellow split color propagation! Fortunately this JustWorksTM on the gt path, since it mostly looks like a normal trunk after stitching. But we completely ignore yellow split color propagation on the le path since this runs into many of the same issues as red flipping. But if you manage to make it though all of this mess while preserving color-balance (code size be damned), we arive on what seems to be an impossible case: How do you preserve color balance of a diverging alt when both paths contain a pending yellow split? .-> .-> .-> h=4 -. .-----b-> .-b-> .-b-> | | .-> | .-> | .-> | | .---b-> .-y-b-> .-y-b-> | | | .-> | .-> | .-> | | | .-b-> | .-b-> | .-b-> | | | | .-> b-b-b-> .-b-b-b-> | .-y-r-b-b-> rm me => => | +- unbalanced :( | .-> r-b-b-b-> | | .-----b-> | | '-> | | | .-> | | .-> | | | .---b-> | '-b-> | | | | .-> | .-> | | | | .-b-> | .-b-> | | | | | .-> | | .-> | b-y-r-b-b-> '-b-b-> h=3 -' ^ ^ diverging lost color propagation This seems to violate tail recursion! Anyways, this turned into a bit of a rant and a bit of a mess. If anyone reads this and is interested in exploring the balancing issues further, the diverging alt logic currently contains some commented-out coloring conditions: (true) / (false) / (lfsr_tag_isred(p_alts[0])) These are currently commented-out to what is currently known to be optimal (see above), but can be tweaked to try to preserve different colorings. --- lfs.c | 172 +++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 147 insertions(+), 25 deletions(-) diff --git a/lfs.c b/lfs.c index c1a6e54d..804932a2 100644 --- a/lfs.c +++ b/lfs.c @@ -2802,6 +2802,10 @@ static int lfsr_rbyd_appendattr(lfs_t *lfs, lfsr_rbyd_t *rbyd, return 0; } + printf("%04x->%04x: --- appendattr ---\n", + lfsr_rbyd_trunk(rbyd), + rbyd->eoff); + // begin appending int err = lfsr_rbyd_prepareappend(lfs, rbyd); if (err) { @@ -2929,6 +2933,14 @@ again:; return d; } + printf("%04x->%04x: tag 0x%x w%d (%d %d)\n", + branch, + rbyd->eoff, + alt, + weight, + lower_rid, + upper_rid); + // found an alt? if (lfsr_tag_isalt(alt)) { // make jump absolute @@ -2937,14 +2949,22 @@ again:; // do bounds want to take different paths? begin diverging if (!lfsr_d_isdiverged(d_state) - && (lfsr_tag_follow2(alt, weight, + && lfsr_tag_follow2(alt, weight, p_alts[0], p_weights[0], lower_rid, upper_rid, a_rid, a_tag) ^ lfsr_tag_follow2(alt, weight, p_alts[0], p_weights[0], lower_rid, upper_rid, - b_rid, b_tag))) { + b_rid, b_tag)) { + printf("%04x->%04x: %cdiverge 0x%x w%d 0x%x w%d\n", + branch, + rbyd->eoff, + lfsr_tag_isred(alt) ? 'r' : 'b', + alt, + weight, + p_alts[0], + p_weights[0]); LFS_ASSERT(d_state != LFSR_D_NOTDIVERGING); // transition to the diverged state @@ -2953,36 +2973,96 @@ again:; // caught on the previous pass d_state = lfsr_d_diverge(d_state); - if (lfsr_tag_follow2( - alt, weight, - p_alts[0], p_weights[0], - lower_rid, upper_rid, - a_rid, a_tag)) { - lfsr_tag_flip2( - &alt, &weight, + // TODO trim or something? + if (d_state != LFSR_D_DIVERGEDUPPER) { + if (lfsr_tag_follow2( + alt, weight, p_alts[0], p_weights[0], - lower_rid, upper_rid); - lfs_swap32(&jump, &branch_); + lower_rid, upper_rid, + a_rid, a_tag)) { + lfsr_tag_flip2( + &alt, &weight, + p_alts[0], p_weights[0], + lower_rid, upper_rid); + lfs_swap32(&jump, &branch_); + } + + lfsr_tag_trim2( + alt, weight, + p_alts[0], p_weights[0], + &lower_rid, &upper_rid, + &lower_tag, &upper_tag); } - lfsr_tag_trim2( - alt, weight, - p_alts[0], p_weights[0], - &lower_rid, &upper_rid, - &lower_tag, &upper_tag); // stitch together diverged branches if (d_state == LFSR_D_DIVERGEDUPPER) { - err = lfsr_rbyd_p_push(lfs, rbyd, - p_alts, p_weights, p_jumps, - LFSR_TAG_ALT( + if (lfsr_tag_isle(alt)) { + alt = LFSR_TAG_ALT( LFSR_TAG_LE, - LFSR_TAG_B, - d_tag), - d_rid - lower_rid + weight, - d_branch); - if (err) { - return err; + (!lfsr_tag_isred(p_alts[0])) + ? (LFSR_TAG_R & alt) + : LFSR_TAG_B, + d_tag); + printf("%04x->%04x: dle 0x%x %d w%d (%d %d)\n", + branch, + rbyd->eoff, + d_tag, + d_rid, + weight, + lower_rid, + upper_rid); + lower_rid += weight; + weight = d_rid - lower_rid + weight; + if (lfsr_tag_isred(p_alts[0]) + && lfsr_tag_isle(p_alts[0])) { + weight -= p_weights[0]; + } + lower_rid -= weight; + jump = d_branch; + } else { + lfsr_tag_flip2( + &alt, &weight, + p_alts[0], p_weights[0], + lower_rid, upper_rid); + alt = LFSR_TAG_ALT( + LFSR_TAG_LE, + (false) // (!lfsr_tag_isred(p_alts[0])) + ? (LFSR_TAG_R & alt) + : LFSR_TAG_B, + d_tag); + printf("%04x->%04x: dgt 0x%x %d w%d (%d %d)\n", + branch, + rbyd->eoff, + d_tag, + d_rid, + weight, + lower_rid, + upper_rid); + lower_rid += weight; + weight = d_rid - lower_rid + weight; + if (lfsr_tag_isred(p_alts[0]) + && lfsr_tag_isle(p_alts[0])) { + weight -= p_weights[0]; + } + lower_rid -= weight; + lfsr_tag_flip2( + &alt, &weight, + p_alts[0], p_weights[0], + lower_rid, upper_rid); + branch_ = d_branch; } + + printf("%04x->%04x: dtag 0x%x w%d (%d %d)\n", + branch, + rbyd->eoff, + alt, + weight, + lower_rid, + upper_rid); + + // TODO doc + y_branch = d_branch; + goto dont_trim_me; } branch = branch_; @@ -3018,6 +3098,7 @@ again:; weight = 0; } + dont_trim_me:; // prune? // b // .-'| .-'| @@ -3050,6 +3131,11 @@ again:; // prune unreachable red-black alts if (lfsr_tag_isred(p_alts[0])) { + printf("%04x->%04x: rbprune 0x%x w%d\n", + branch, + rbyd->eoff, + alt, + weight); alt = p_alts[0] & ~LFSR_TAG_R; weight = p_weights[0]; jump = p_jumps[0]; @@ -3059,12 +3145,22 @@ again:; } else if (lfsr_tag_isred(alt) // prune unreachable black alts if root || (!p_alts[0] && !lfsr_d_isdiverged(d_state))) { + printf("%04x->%04x: rprune 0x%x w%d\n", + branch, + rbyd->eoff, + alt, + weight); branch = branch_; continue; // convert unreachable non-root black alts into alt-nevers, // if we prune these it would break the coloring of our tree } else { + printf("%04x->%04x: bprune 0x%x w%d\n", + branch, + rbyd->eoff, + alt, + weight); alt = LFSR_TAG_ALT(LFSR_TAG_LE, LFSR_TAG_B, 0); weight = 0; jump = 0; @@ -3089,6 +3185,16 @@ again:; p_alts[0], p_weights[0], lower_rid, upper_rid, a_rid, a_tag)) { + printf("%04x->%04x: ysplit1 0x%x w%d 0x%x w%d (%x %x %x)\n", + branch, + rbyd->eoff, + alt, + weight, + p_alts[0], + p_weights[0], + p_jumps[0], + jump, + branch_); lfsr_tag_flip2(&alt, &weight, p_alts[0], p_weights[0], lower_rid, upper_rid); @@ -3117,6 +3223,16 @@ again:; // | | .-'| | | .----'| // 1 2 3 4 1 2 3 4 4 } else { + printf("%04x->%04x: ysplit2 0x%x w%d 0x%x w%d (%x %x %x)\n", + branch, + rbyd->eoff, + alt, + weight, + p_alts[0], + p_weights[0], + p_jumps[0], + jump, + branch_); LFS_ASSERT(y_branch != 0); p_alts[0] = alt; p_weights[0] += weight; @@ -3207,11 +3323,17 @@ again:; // no divergence? guess we only need one trunk then, actually write // it out this time if (d_state == LFSR_D_DIVERGINGLOWER) { + printf("%04x->%04x: not diverging\n", + branch, + rbyd->eoff); d_state = LFSR_D_NOTDIVERGING; goto again; // diverged lower trunk? we need an upper trunk too } else if (d_state == LFSR_D_DIVERGEDLOWER) { + printf("%04x->%04x: diverging again\n", + branch, + rbyd->eoff); // keep track of last alt on diverged trunk to stitch the trunks // together with d_state = LFSR_D_DIVERGINGUPPER;