rbyd-rr: Attempting correct balance of the diverging node itself

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.
This commit is contained in:
Christopher Haster
2024-04-03 21:54:21 -05:00
parent 4f14f3cef4
commit 233fc2c212
+133 -11
View File
@@ -2802,6 +2802,10 @@ static int lfsr_rbyd_appendattr(lfs_t *lfs, lfsr_rbyd_t *rbyd,
return 0; return 0;
} }
printf("%04x->%04x: --- appendattr ---\n",
lfsr_rbyd_trunk(rbyd),
rbyd->eoff);
// begin appending // begin appending
int err = lfsr_rbyd_prepareappend(lfs, rbyd); int err = lfsr_rbyd_prepareappend(lfs, rbyd);
if (err) { if (err) {
@@ -2929,6 +2933,14 @@ again:;
return d; 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? // found an alt?
if (lfsr_tag_isalt(alt)) { if (lfsr_tag_isalt(alt)) {
// make jump absolute // make jump absolute
@@ -2937,14 +2949,22 @@ again:;
// do bounds want to take different paths? begin diverging // do bounds want to take different paths? begin diverging
if (!lfsr_d_isdiverged(d_state) if (!lfsr_d_isdiverged(d_state)
&& (lfsr_tag_follow2(alt, weight, && lfsr_tag_follow2(alt, weight,
p_alts[0], p_weights[0], p_alts[0], p_weights[0],
lower_rid, upper_rid, lower_rid, upper_rid,
a_rid, a_tag) a_rid, a_tag)
^ lfsr_tag_follow2(alt, weight, ^ lfsr_tag_follow2(alt, weight,
p_alts[0], p_weights[0], p_alts[0], p_weights[0],
lower_rid, upper_rid, 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); LFS_ASSERT(d_state != LFSR_D_NOTDIVERGING);
// transition to the diverged state // transition to the diverged state
@@ -2953,6 +2973,8 @@ again:;
// caught on the previous pass // caught on the previous pass
d_state = lfsr_d_diverge(d_state); d_state = lfsr_d_diverge(d_state);
// TODO trim or something?
if (d_state != LFSR_D_DIVERGEDUPPER) {
if (lfsr_tag_follow2( if (lfsr_tag_follow2(
alt, weight, alt, weight,
p_alts[0], p_weights[0], p_alts[0], p_weights[0],
@@ -2964,25 +2986,83 @@ again:;
lower_rid, upper_rid); lower_rid, upper_rid);
lfs_swap32(&jump, &branch_); lfs_swap32(&jump, &branch_);
} }
lfsr_tag_trim2( lfsr_tag_trim2(
alt, weight, alt, weight,
p_alts[0], p_weights[0], p_alts[0], p_weights[0],
&lower_rid, &upper_rid, &lower_rid, &upper_rid,
&lower_tag, &upper_tag); &lower_tag, &upper_tag);
}
// stitch together diverged branches // stitch together diverged branches
if (d_state == LFSR_D_DIVERGEDUPPER) { if (d_state == LFSR_D_DIVERGEDUPPER) {
err = lfsr_rbyd_p_push(lfs, rbyd, if (lfsr_tag_isle(alt)) {
p_alts, p_weights, p_jumps, alt = LFSR_TAG_ALT(
LFSR_TAG_ALT(
LFSR_TAG_LE, LFSR_TAG_LE,
LFSR_TAG_B, (!lfsr_tag_isred(p_alts[0]))
d_tag), ? (LFSR_TAG_R & alt)
d_rid - lower_rid + weight, : LFSR_TAG_B,
d_branch); d_tag);
if (err) { printf("%04x->%04x: dle 0x%x %d w%d (%d %d)\n",
return err; 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_; branch = branch_;
@@ -3018,6 +3098,7 @@ again:;
weight = 0; weight = 0;
} }
dont_trim_me:;
// prune? // prune?
// <b >b // <b >b
// .-'| .-'| // .-'| .-'|
@@ -3050,6 +3131,11 @@ again:;
// prune unreachable red-black alts // prune unreachable red-black alts
if (lfsr_tag_isred(p_alts[0])) { 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; alt = p_alts[0] & ~LFSR_TAG_R;
weight = p_weights[0]; weight = p_weights[0];
jump = p_jumps[0]; jump = p_jumps[0];
@@ -3059,12 +3145,22 @@ again:;
} else if (lfsr_tag_isred(alt) } else if (lfsr_tag_isred(alt)
// prune unreachable black alts if root // prune unreachable black alts if root
|| (!p_alts[0] && !lfsr_d_isdiverged(d_state))) { || (!p_alts[0] && !lfsr_d_isdiverged(d_state))) {
printf("%04x->%04x: rprune 0x%x w%d\n",
branch,
rbyd->eoff,
alt,
weight);
branch = branch_; branch = branch_;
continue; continue;
// convert unreachable non-root black alts into alt-nevers, // convert unreachable non-root black alts into alt-nevers,
// if we prune these it would break the coloring of our tree // if we prune these it would break the coloring of our tree
} else { } 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); alt = LFSR_TAG_ALT(LFSR_TAG_LE, LFSR_TAG_B, 0);
weight = 0; weight = 0;
jump = 0; jump = 0;
@@ -3089,6 +3185,16 @@ again:;
p_alts[0], p_weights[0], p_alts[0], p_weights[0],
lower_rid, upper_rid, lower_rid, upper_rid,
a_rid, a_tag)) { 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, lfsr_tag_flip2(&alt, &weight,
p_alts[0], p_weights[0], p_alts[0], p_weights[0],
lower_rid, upper_rid); lower_rid, upper_rid);
@@ -3117,6 +3223,16 @@ again:;
// | | .-'| | | .----'| // | | .-'| | | .----'|
// 1 2 3 4 1 2 3 4 4 // 1 2 3 4 1 2 3 4 4
} else { } 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); LFS_ASSERT(y_branch != 0);
p_alts[0] = alt; p_alts[0] = alt;
p_weights[0] += weight; p_weights[0] += weight;
@@ -3207,11 +3323,17 @@ again:;
// no divergence? guess we only need one trunk then, actually write // no divergence? guess we only need one trunk then, actually write
// it out this time // it out this time
if (d_state == LFSR_D_DIVERGINGLOWER) { if (d_state == LFSR_D_DIVERGINGLOWER) {
printf("%04x->%04x: not diverging\n",
branch,
rbyd->eoff);
d_state = LFSR_D_NOTDIVERGING; d_state = LFSR_D_NOTDIVERGING;
goto again; goto again;
// diverged lower trunk? we need an upper trunk too // diverged lower trunk? we need an upper trunk too
} else if (d_state == LFSR_D_DIVERGEDLOWER) { } 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 // keep track of last alt on diverged trunk to stitch the trunks
// together with // together with
d_state = LFSR_D_DIVERGINGUPPER; d_state = LFSR_D_DIVERGINGUPPER;