Preserve coloring on range removals

This rearranges diverged pruning in lfsr_rbyd_appendattr a bit to try to
avoid unnecessary r->b recoloring during range removals.

Two tweaks:

1. lfsr_rbyd_appendattr now only alternates diverged paths on a black
   edge.

   This is equivalent to alternating on the underlying 2-3-4 tree,
   and means we don't have to worry about overlapping red edges from
   the two diverged paths interacting with each other in weird ways.

2. Thanks to alternating on a black edge, we can now prune diverged
   paths before applying our red-black-yellow operations.

   This means we can avoid the somewhat-hack that was r->b recoloring
   (if you paint it black, red-yellow operations are skipped so nothing
   breaks trivially, but you also break your tree balance invariants).
   So the two diverged paths should remain strictly 2(log n)+1, at least
   in isolation.

   A nice side-effect of moving the diverged-prune code is we can
   deduplicate pruning with yellow-edge prunning. This saves a branch of
   code, though it will makes things a bit more confusing if anyone
   tries to use lfsr_rbyd_appendattr as a template for an rbyd
   implementation without range removals.

The new pruning conditions are more complex, resulting in more code
cost, but this will be worth it if it results in better tree balance
after range removals:

  before: 33912          2880
  after:  34064 (+0.4%)  2880 (+0.0%)

Some quick, non-rigorous benchmarks showed a noticable, but tiny
improvement in random write performance. Though I'm not sure random
writes are likely to hit degenerate range removals...
This commit is contained in:
Christopher Haster
2024-03-01 00:40:02 -06:00
parent 907d6b7038
commit a6a5f43027
+49 -17
View File
@@ -2798,13 +2798,13 @@ static int lfsr_rbyd_appendattr(lfs_t *lfs, lfsr_rbyd_t *rbyd,
jump = branch - jump;
lfs_size_t branch_ = branch + d;
// do bounds want to take different paths? begin cutting
// do bounds want to take different paths? begin diverging
if (!lfsr_tag_hasdiverged(tag_)
&& lfsr_tag_follow2(alt, weight,
p_alts[0], p_weights[0],
lower_rid, upper_rid,
rid_, tag_)
!= lfsr_tag_follow2(alt, weight,
^ lfsr_tag_follow2(alt, weight,
p_alts[0], p_weights[0],
lower_rid, upper_rid,
other_rid_, other_tag_)) {
@@ -2826,13 +2826,6 @@ static int lfsr_rbyd_appendattr(lfs_t *lfs, lfsr_rbyd_t *rbyd,
}
}
// if we're diverging, go ahead and make alt black, this isn't
// perfect but it's simpler and compact will take care of any
// balance issues that may occur
if (lfsr_tag_hasdiverged(tag_)) {
alt &= ~LFSR_TAG_R;
}
// prune?
// <b >b
// .-'| .-'|
@@ -2847,7 +2840,32 @@ static int lfsr_rbyd_appendattr(lfs_t *lfs, lfsr_rbyd_t *rbyd,
alt, weight,
p_alts[0], p_weights[0],
lower_rid, upper_rid,
lower_tag, upper_tag)) {
lower_tag, upper_tag)
// prune because of diverged paths?
|| (lfsr_tag_hasdiverged(tag_)
&& lfsr_tag_isdivergedupper(tag_)
^ lfsr_tag_isgt(alt)
^ lfsr_tag_follow2(
alt, weight,
p_alts[0], p_weights[0],
lower_rid, upper_rid,
rid_, tag_))) {
// note yellow prunes always follow and have no weight, it's
// only the diverged paths that need all these special cases
if (lfsr_tag_follow2(
alt, weight,
p_alts[0], p_weights[0],
lower_rid, upper_rid,
rid_, tag_)) {
lfsr_tag_flip2(
&alt, &weight,
p_alts[0], p_weights[0],
lower_rid, upper_rid);
lfsr_tag_trim(
alt, weight,
&lower_rid, &upper_rid,
&lower_tag, &upper_tag);
if (lfsr_tag_isred(p_alts[0])) {
alt = p_alts[0] & ~LFSR_TAG_R;
weight = p_weights[0];
@@ -2855,9 +2873,27 @@ static int lfsr_rbyd_appendattr(lfs_t *lfs, lfsr_rbyd_t *rbyd,
jump = p_jumps[0];
lfsr_rbyd_p_pop(p_alts, p_weights, p_jumps);
} else {
graft = branch;
branch = jump;
continue;
}
} else {
lfsr_tag_trim(
alt, weight,
&lower_rid, &upper_rid,
&lower_tag, &upper_tag);
if (lfsr_tag_isred(p_alts[0])) {
alt = p_alts[0] & ~LFSR_TAG_R;
weight = p_weights[0];
jump = p_jumps[0];
lfsr_rbyd_p_pop(p_alts, p_weights, p_jumps);
} else {
graft = branch;
branch = branch_;
continue;
}
}
}
// two reds makes a yellow, split?
@@ -2972,12 +3008,6 @@ static int lfsr_rbyd_appendattr(lfs_t *lfs, lfsr_rbyd_t *rbyd,
graft = branch;
branch = branch_;
// prune inner alts if our tags diverged
if (lfsr_tag_hasdiverged(tag_)
&& lfsr_tag_isdivergedupper(tag_) != lfsr_tag_isgt(alt)) {
continue;
}
// push alt onto our queue
int err = lfsr_rbyd_p_push(lfs, rbyd,
p_alts, p_weights, p_jumps,
@@ -3002,7 +3032,9 @@ static int lfsr_rbyd_appendattr(lfs_t *lfs, lfsr_rbyd_t *rbyd,
}
// switch to the other path if we have diverged
if (lfsr_tag_hasdiverged(tag_) || !lfsr_tag_isalt(alt)) {
if (!lfsr_tag_isalt(alt)
|| (lfsr_tag_hasdiverged(tag_)
&& lfsr_tag_isblack(p_alts[0]))) {
lfs_swap16(&tag_, &other_tag_);
lfs_sswap32(&rid_, &other_rid_);
lfs_swap32(&branch, &other_branch);