Changed btree merge to merge siblings via compaction

This extends lfsr_rbyd_compact to support compaction of any number of
rbyds (though we only ever compact 1 or 2), and leverages this to
compact both siblings during btree merges.

This should improve erased storage utilization for btree merges, help
maintain a better balance in the tree (since more merges can complete
successfully), and hopefully lessen the impact of repeated merge+splits.

Since merges are now compacted, we can also be sure the combined merge
fits in 1/2 our block (hand-waving the split name for now, though this
does need to be considered when determining btree commit limits). This
lets us move the merge code entirely before writing out the attr-list,
making this operation more in line with split/compact and offering more
chance as code deduplication.

            code          stack
  before:  20626           1728
  after:   20634 (+0.0%)   1712 (-0.9%)

This also ironically discards the previous work to find a simple
estimate of upper bound of uncompacted rbyds, though I'm sure that will
useful again at some point in the future.
This commit is contained in:
Christopher Haster
2023-08-18 01:17:17 -05:00
parent 5ecdc8b4f7
commit de171e57f5
+118 -118
View File
@@ -3003,9 +3003,9 @@ static int lfsr_rbyd_commit(lfs_t *lfs, lfsr_rbyd_t *rbyd,
}
static int lfsr_rbyd_compact(lfs_t *lfs, lfsr_rbyd_t *rbyd,
static int lfsr_rbyd_compact_(lfs_t *lfs, lfsr_rbyd_t *rbyd,
lfs_ssize_t start_rid, lfs_ssize_t end_rid,
const lfsr_rbyd_t *source) {
const lfsr_rbyd_t *const *rbyds, lfs_size_t rbyd_count) {
#ifndef LFSR_NO_REBALANCE
// must fetch before mutating!
LFS_ASSERT(lfsr_rbyd_isfetched(rbyd));
@@ -3031,17 +3031,20 @@ static int lfsr_rbyd_compact(lfs_t *lfs, lfsr_rbyd_t *rbyd,
// first copy over raw tags, note this doesn't create a tree
lfs_off_t layer_off = rbyd->eoff;
lfs_ssize_t bid = 0;
for (lfs_size_t i = 0; i < rbyd_count; i++) {
lfs_ssize_t rid = start_rid;
lfsr_tag_t tag = 0;
while (true) {
lfs_size_t weight;
lfsr_data_t data;
int err = lfsr_rbyd_lookupnext(lfs, source, rid, lfsr_tag_next(tag),
int err = lfsr_rbyd_lookupnext(lfs, rbyds[i],
rid, lfsr_tag_next(tag),
&rid, &tag, &weight, &data);
if (err && err != LFS_ERR_NOENT) {
return err;
}
if (err == LFS_ERR_NOENT || (end_rid >= 0 && rid >= end_rid)) {
if (err == LFS_ERR_NOENT || (end_rid >= 0 && bid+rid >= end_rid)) {
break;
}
@@ -3075,6 +3078,9 @@ static int lfsr_rbyd_compact(lfs_t *lfs, lfsr_rbyd_t *rbyd,
layer_weight += weight;
}
bid += rbyds[i]->weight;
}
// connect every other trunk together, building layers of a perfectly
// balanced binary tree upwards until we have a single trunk
while (layer_trunks > 1) {
@@ -3201,6 +3207,20 @@ failed:;
#endif
}
static int lfsr_rbyd_compact(lfs_t *lfs, lfsr_rbyd_t *rbyd_,
lfs_ssize_t start_rid, lfs_ssize_t end_rid,
const lfsr_rbyd_t *rbyd) {
return lfsr_rbyd_compact_(lfs, rbyd_, start_rid, end_rid,
(const lfsr_rbyd_t *const[1]){rbyd}, 1);
}
static int lfsr_rbyd_merge(lfs_t *lfs, lfsr_rbyd_t *rbyd_,
lfs_ssize_t start_rid, lfs_ssize_t end_rid,
const lfsr_rbyd_t *rbyd, const lfsr_rbyd_t *sibling) {
return lfsr_rbyd_compact_(lfs, rbyd_, start_rid, end_rid,
(const lfsr_rbyd_t *const[2]){rbyd, sibling}, 2);
}
// the following are mostly btree helpers, but since they operate on rbyds,
// exist in the rbyd namespace
@@ -3965,10 +3985,7 @@ static int lfsr_btree_commit(lfs_t *lfs, lfsr_btree_t *btree,
goto commit;
compact:;
// can't commit, try to compact
// check if we're within our compaction threshold, otherwise we
// need to split
// estimate our compacted size
lfs_size_t split_rid;
lfs_ssize_t estimate = lfsr_rbyd_estimateall(lfs, &rbyd, -1, -1,
&split_rid);
@@ -3976,63 +3993,30 @@ static int lfsr_btree_commit(lfs_t *lfs, lfsr_btree_t *btree,
return estimate;
}
// are we too big? need to split?
if ((lfs_size_t)estimate > lfs->cfg->block_size/2) {
// need to split
goto split;
}
// allocate a new rbyd
err = lfsr_rbyd_alloc(lfs, &rbyd_);
if (err) {
return err;
}
// try to compact
err = lfsr_rbyd_compact(lfs, &rbyd_, -1, -1, &rbyd);
if (err) {
LFS_ASSERT(err != LFS_ERR_RANGE);
return err;
}
// append any pending attrs, it's up to upper
// layers to make sure these always fit
err = lfsr_rbyd_appendall(lfs, &rbyd_, bid, -1, -1,
attrs, attr_count);
if (err) {
LFS_ASSERT(err != LFS_ERR_RANGE);
return err;
}
// is our compacted size too small? can we merge with one of our
// siblings?
// before we compact, can we merge with our siblings?
lfsr_rbyd_t sibling;
lfs_ssize_t sibling_rid;
lfs_ssize_t sibling_delta;
if (rbyd_.eoff <= lfs->cfg->block_size/4
// don't merge if our rbyd went to zero, just drop
&& rbyd_.weight > 0
// no parent? can't merge
&& rid != -1) {
for (uint8_t i = 0; i < 2; i++) {
lfs_ssize_t sibling_rid;
// try the right sibling
if (i == 0) {
// right-most child? can't merge
if ((lfs_size_t)rid == parent.weight-1) {
continue;
}
sibling_rid = rid+1;
sibling_delta = rbyd_.weight;
// try the left sibling
} else {
// left-most child? can't merge
if ((lfs_size_t)rid-(rbyd.weight-1) == 0) {
continue;
sibling_rid = rid-rbyd.weight;
}
sibling_rid = rid-rbyd.weight;
sibling_delta = 0;
// no parent? no sibling?
if (rid == -1
|| sibling_rid < 0
|| sibling_rid >= (lfs_ssize_t)parent.weight) {
continue;
}
// try looking up the sibling
@@ -4071,27 +4055,52 @@ static int lfsr_btree_commit(lfs_t *lfs, lfsr_btree_t *btree,
}
// estimate if our sibling will fit
lfs_ssize_t estimate = lfsr_rbyd_estimateall(lfs,
lfs_ssize_t sibling_estimate = lfsr_rbyd_estimateall(lfs,
&sibling, -1, -1,
NULL);
if (estimate < 0) {
if (sibling_estimate < 0) {
return estimate;
}
// doesn't fit? can't merge
//
// note we use our uncompacted estimate here, since we need to
// make sure our commit that merges the sibling doesn't fail
if (estimate * lfs_nlog2((rbyd_.eoff+estimate)/16)
> lfs->cfg->block_size/4) {
continue;
}
// fits? try to merge
if ((lfs_size_t)(estimate + sibling_estimate)
< lfs->cfg->block_size/2) {
if (i == 1) {
// if we're merging our left sibling, swap our rbyds
// so our sibling is on the right
bid -= sibling.weight;
rid -= rbyd.weight;
// found a sibling that can be merged
rbyd_ = sibling;
sibling = rbyd;
rbyd = rbyd_;
}
goto merge;
}
}
// allocate a new rbyd
err = lfsr_rbyd_alloc(lfs, &rbyd_);
if (err) {
return err;
}
// try to compact
err = lfsr_rbyd_compact(lfs, &rbyd_, -1, -1, &rbyd);
if (err) {
LFS_ASSERT(err != LFS_ERR_RANGE);
return err;
}
// append any pending attrs, it's up to upper
// layers to make sure these always fit
err = lfsr_rbyd_appendall(lfs, &rbyd_, bid, -1, -1,
attrs, attr_count);
if (err) {
LFS_ASSERT(err != LFS_ERR_RANGE);
return err;
}
// finalize commit
err = lfsr_rbyd_appendcksum(lfs, &rbyd_);
if (err) {
@@ -4130,11 +4139,13 @@ static int lfsr_btree_commit(lfs_t *lfs, lfsr_btree_t *btree,
scratch_attrs[0] = LFSR_ATTR(
bid+rid, BRANCH, 0,
BUF(scratch_buf, scratch_dsize));
scratch_attrs[1] = LFSR_ATTR(
bid+rid,
TAG(rbyd_.weight > 0 ? LFSR_TAG_GROW(RM) : LFSR_TAG_RM),
+rbyd_.weight-rbyd.weight,
NULL);
scratch_attrs[1] = (rbyd_.weight == 0
? LFSR_ATTR(
bid+rid, RM, -rbyd.weight,
NULL)
: LFSR_ATTR(
bid+rid, GROW(RM), +rbyd_.weight-rbyd.weight,
NULL));
attrs = scratch_attrs;
attr_count = 2;
@@ -4229,19 +4240,6 @@ static int lfsr_btree_commit(lfs_t *lfs, lfsr_btree_t *btree,
return err;
}
uint8_t *scratch1_buf = scratch_buf;
uint8_t *scratch2_buf = scratch_buf + LFSR_BRANCH_DSIZE;
lfs_ssize_t scratch1_dsize = lfsr_branch_todisk(lfs, &rbyd_,
scratch1_buf);
if (scratch1_dsize < 0) {
return scratch1_dsize;
}
lfs_ssize_t scratch2_dsize = lfsr_branch_todisk(lfs, &sibling,
scratch2_buf);
if (scratch2_dsize < 0) {
return scratch2_dsize;
}
// no parent? introduce a new root
if (rid == -1) {
LFS_ASSERT(bid == 0);
@@ -4256,6 +4254,19 @@ static int lfsr_btree_commit(lfs_t *lfs, lfsr_btree_t *btree,
rbyd.weight = 0;
}
uint8_t *scratch1_buf = scratch_buf;
uint8_t *scratch2_buf = scratch_buf + LFSR_BRANCH_DSIZE;
lfs_ssize_t scratch1_dsize = lfsr_branch_todisk(lfs, &rbyd_,
scratch1_buf);
if (scratch1_dsize < 0) {
return scratch1_dsize;
}
lfs_ssize_t scratch2_dsize = lfsr_branch_todisk(lfs, &sibling,
scratch2_buf);
if (scratch2_dsize < 0) {
return scratch2_dsize;
}
// prepare commit to parent, tail recursing upwards
bid -= rid - (rbyd.weight-1);
LFS_ASSERT(rbyd_.weight > 0);
@@ -4281,35 +4292,22 @@ static int lfsr_btree_commit(lfs_t *lfs, lfsr_btree_t *btree,
continue;
merge:;
// try to add our sibling's tags to our rbyd
lfsr_rbyd_t rbyd__ = rbyd_;
lfs_ssize_t rid_ = 0;
lfsr_tag_t tag_ = 0;
while (true) {
lfs_size_t weight_;
lfsr_data_t data_;
err = lfsr_rbyd_lookupnext(lfs, &sibling, rid_, lfsr_tag_next(tag_),
&rid_, &tag_, &weight_, &data_);
if (err && err != LFS_ERR_NOENT) {
// allocate a new rbyd
err = lfsr_rbyd_alloc(lfs, &rbyd_);
if (err) {
return err;
}
if (err == LFS_ERR_NOENT) {
break;
}
// append the attr
err = lfsr_rbyd_append(lfs, &rbyd__,
sibling_delta+rid_-lfs_smax32(weight_-1, 0), tag_, +weight_,
data_);
// merge the siblings together
err = lfsr_rbyd_merge(lfs, &rbyd_, -1, -1, &rbyd, &sibling);
if (err) {
LFS_ASSERT(err != LFS_ERR_RANGE);
return err;
}
}
// bring in name that previously split the siblings
err = lfsr_rbyd_lookupnext(lfs, &parent,
(sibling_delta == 0 ? rid : sibling_rid), LFSR_TAG_NAME,
rid+1, LFSR_TAG_NAME,
NULL, &split_tag, NULL, &split_data);
if (err) {
return err;
@@ -4318,16 +4316,15 @@ static int lfsr_btree_commit(lfs_t *lfs, lfsr_btree_t *btree,
if (lfsr_tag_suptype(split_tag) == LFSR_TAG_NAME) {
// lookup the rid (weight really) of the previously-split entry
lfs_ssize_t split_rid;
err = lfsr_rbyd_lookupnext(lfs, &rbyd__,
(sibling_delta == 0 ? sibling.weight : rbyd_.weight),
LFSR_TAG_NAME,
err = lfsr_rbyd_lookupnext(lfs, &rbyd_,
rbyd.weight, LFSR_TAG_NAME,
&split_rid, NULL, NULL, NULL);
if (err) {
LFS_ASSERT(err != LFS_ERR_NOENT);
return err;
}
err = lfsr_rbyd_append(lfs, &rbyd__,
err = lfsr_rbyd_append(lfs, &rbyd_,
split_rid, LFSR_TAG_BNAME, 0, split_data);
if (err) {
LFS_ASSERT(err != LFS_ERR_RANGE);
@@ -4335,8 +4332,17 @@ static int lfsr_btree_commit(lfs_t *lfs, lfsr_btree_t *btree,
}
}
// append any pending attrs, it's up to upper
// layers to make sure these always fit
err = lfsr_rbyd_appendall(lfs, &rbyd_, bid, -1, -1,
attrs, attr_count);
if (err) {
LFS_ASSERT(err != LFS_ERR_RANGE);
return err;
}
// finalize the commit
err = lfsr_rbyd_appendcksum(lfs, &rbyd__);
err = lfsr_rbyd_appendcksum(lfs, &rbyd_);
if (err) {
LFS_ASSERT(err != LFS_ERR_RANGE);
return err;
@@ -4347,33 +4353,26 @@ static int lfsr_btree_commit(lfs_t *lfs, lfsr_btree_t *btree,
LFS_ASSERT(rid != -1);
if (rbyd.weight+sibling.weight == lfsr_btree_weight(btree)) {
// collapse the root, decreasing the height of the tree
btree->u.r.rbyd = rbyd__;
btree->u.r.rbyd = rbyd_;
return 0;
}
// prepare commit to parent, tail recursing upwards
bid -= rid - (rbyd.weight-1);
// make rid the lower child so the following math is easier
if (rid > sibling_rid) {
lfs_sswap32(&rid, &sibling_rid);
lfs_swap32(&rbyd.weight, &sibling.weight);
}
scratch_dsize = lfsr_branch_todisk(lfs, &rbyd__,
scratch_dsize = lfsr_branch_todisk(lfs, &rbyd_,
scratch_buf);
if (scratch_dsize < 0) {
return scratch_dsize;
}
LFS_ASSERT(rbyd__.weight > 0);
// prepare commit to parent, tail recursing upwards
bid -= rid - (rbyd.weight-1);
LFS_ASSERT(rbyd_.weight > 0);
scratch_attrs[0] = LFSR_ATTR(
bid+sibling_rid, RM, -sibling.weight, NULL);
bid+rid+sibling.weight, RM, -sibling.weight, NULL);
scratch_attrs[1] = LFSR_ATTR(
bid+rid, BRANCH, 0,
BUF(scratch_buf, scratch_dsize));
scratch_attrs[2] = LFSR_ATTR(
bid+rid, GROW(RM), +rbyd__.weight-rbyd.weight, NULL);
bid+rid, GROW(RM), +rbyd_.weight-rbyd.weight, NULL);
attrs = scratch_attrs;
attr_count = 3;
@@ -5005,7 +5004,8 @@ static int lfsr_mdir_compact_(lfs_t *lfs, lfsr_mdir_t *mdir_,
// copy over attrs
err = lfsr_rbyd_compact(lfs, &mdir_->u.r.rbyd,
start_rid, end_rid, &mdir->u.r.rbyd);
start_rid, end_rid,
&mdir->u.r.rbyd);
if (err) {
LFS_ASSERT(err != LFS_ERR_RANGE);
return err;