Tweaked btree traversal to visit each leaf at most once
We are already paying the memory cost of a fetched lfsr_rbyd_t during btree so we can traverse inner btree nodes. But we are currently just wasting this memory when we traverse leaf entries. Instead, we can use this memory to cache the btree's current leaf node, avoiding a btree walk until we've iterated over all rids in the current leaf. Think about this for a second: 1. We cache the root rbyd in lfsr_btree_t because we share the memory with a union and we always need to read the root during lookups. 2. We cache the leaf rbyds in lfsr_btree_traversal_t because we need the memory for traversing inner btree nodes. The only btree nodes we don't cache during traversal are inner nodes when the height of the btree >= 3. If you're familiar with how btrees behave on storage, you know the height because _exponentially_ less likely to grow as the tree gets larger. It's entirely possible for btree traversal to simply never traverse a non-cached btree node once your block size gets large enough. --- There may be a way to instead reclaim this memory, such as sharing this memory with mtree traversal and higher layers, but the current API design and hierarchy of C structs make this difficult. Maybe this is worth looking into in the future, but at most it would save one lfsr_rbyd_t. This also reverts some rid changes in btree lookups to share less code but be a bit easier to reason about.
This commit is contained in:
@@ -3094,11 +3094,11 @@ static int lfsr_btree_lookupnext_(lfs_t *lfs,
|
||||
}
|
||||
}
|
||||
|
||||
// adjust rid with subtree's weight
|
||||
rid -= (rid__ - (weight__-1));
|
||||
|
||||
// found another branch
|
||||
if (tag__ == LFSR_TAG_BTREE) {
|
||||
// adjust rid with subtree's weight
|
||||
rid -= (rid__ - (weight__-1));
|
||||
|
||||
// fetch the next branch
|
||||
lfs_ssize_t d = lfsr_branch_fromdisk(lfs, &branch, data__);
|
||||
if (d < 0) {
|
||||
@@ -3110,7 +3110,7 @@ static int lfsr_btree_lookupnext_(lfs_t *lfs,
|
||||
} else {
|
||||
// TODO how many of these should be conditional?
|
||||
if (bid_) {
|
||||
*bid_ = bid - (rid - (weight__-1));
|
||||
*bid_ = bid + (rid__ - rid);
|
||||
}
|
||||
if (rbyd_) {
|
||||
*rbyd_ = branch;
|
||||
@@ -3348,11 +3348,11 @@ static lfs_ssize_t lfsr_btree_namelookupnext(lfs_t *lfs,
|
||||
}
|
||||
}
|
||||
|
||||
// update our bid
|
||||
bid += rid__ - (weight__-1);
|
||||
|
||||
// found another branch
|
||||
if (tag__ == LFSR_TAG_BTREE) {
|
||||
// update our bid
|
||||
bid += rid__ - (weight__-1);
|
||||
|
||||
// fetch the next branch
|
||||
lfs_ssize_t d = lfsr_branch_fromdisk(lfs, &branch, data__);
|
||||
if (d < 0) {
|
||||
@@ -3364,7 +3364,7 @@ static lfs_ssize_t lfsr_btree_namelookupnext(lfs_t *lfs,
|
||||
} else {
|
||||
// TODO how many of these should be conditional?
|
||||
if (bid_) {
|
||||
*bid_ = bid + (weight__-1);
|
||||
*bid_ = bid + rid__;
|
||||
}
|
||||
if (rbyd_) {
|
||||
*rbyd_ = branch;
|
||||
@@ -4314,7 +4314,6 @@ typedef struct lfsr_btree_traversal {
|
||||
#define LFSR_BTREE_TRAVERSAL_INIT ((lfsr_btree_traversal_t){ \
|
||||
.bid = 0, \
|
||||
.rid = 0, \
|
||||
.branch.block = 0, \
|
||||
.branch.trunk = 0, \
|
||||
.branch.weight = 0, \
|
||||
})
|
||||
@@ -4352,8 +4351,8 @@ static int lfsr_btree_traversal_next(lfs_t *lfs,
|
||||
return 0;
|
||||
}
|
||||
|
||||
// make sure we traverse the root
|
||||
if (traversal->branch.trunk == 0) {
|
||||
// restart from the root
|
||||
if ((lfs_size_t)traversal->rid >= traversal->branch.weight) {
|
||||
traversal->bid += traversal->branch.weight;
|
||||
traversal->rid = traversal->bid;
|
||||
traversal->branch = btree->root;
|
||||
@@ -4404,11 +4403,11 @@ static int lfsr_btree_traversal_next(lfs_t *lfs,
|
||||
}
|
||||
}
|
||||
|
||||
// adjust rid with subtree's weight
|
||||
traversal->rid -= (rid__ - (weight__-1));
|
||||
|
||||
// found another branch
|
||||
if (tag__ == LFSR_TAG_BTREE) {
|
||||
// adjust rid with subtree's weight
|
||||
traversal->rid -= (rid__ - (weight__-1));
|
||||
|
||||
// fetch the next branch
|
||||
lfs_ssize_t d = lfsr_branch_fromdisk(lfs,
|
||||
&traversal->branch, data__);
|
||||
@@ -4422,8 +4421,7 @@ static int lfsr_btree_traversal_next(lfs_t *lfs,
|
||||
if (traversal->rid == 0) {
|
||||
// TODO how many of these should be conditional?
|
||||
if (bid_) {
|
||||
*bid_ = traversal->bid - (
|
||||
traversal->rid - (traversal->branch.weight-1));
|
||||
*bid_ = traversal->bid + (rid__ - traversal->rid);
|
||||
}
|
||||
if (tag_) {
|
||||
*tag_ = LFSR_TAG_BTREE;
|
||||
@@ -4441,13 +4439,16 @@ static int lfsr_btree_traversal_next(lfs_t *lfs,
|
||||
|
||||
// found our bid
|
||||
} else {
|
||||
// update traversal
|
||||
traversal->branch.trunk = 0;
|
||||
traversal->branch.weight = weight__;
|
||||
// move on to the next rid
|
||||
//
|
||||
// note the effectively traverses a full leaf without redoing
|
||||
// the btree walk
|
||||
lfs_ssize_t bid__ = traversal->bid + (rid__ - traversal->rid);
|
||||
traversal->rid = rid__ + 1;
|
||||
|
||||
// TODO how many of these should be conditional?
|
||||
if (bid_) {
|
||||
*bid_ = traversal->bid - (traversal->rid - (weight__-1));
|
||||
*bid_ = bid__;
|
||||
}
|
||||
if (tag_) {
|
||||
*tag_ = tag__;
|
||||
@@ -5538,25 +5539,6 @@ enum {
|
||||
static int lfsr_mtree_traversal_next(lfs_t *lfs,
|
||||
lfsr_mtree_traversal_t *traversal,
|
||||
lfs_size_t *mid_, lfsr_tag_t *tag_, lfsr_data_t *data_) {
|
||||
// detect cycles with Brent's algorithm
|
||||
if (traversal->mtraversal.branch.trunk == 0) {
|
||||
if (traversal->mdir.rbyd.trunk != 0
|
||||
&& lfsr_mpair_eq(lfsr_mdir_mpair(&traversal->mdir),
|
||||
traversal->tortoise_mpair)) {
|
||||
LFS_ERROR("Cycle detected during mtree traversal "
|
||||
"(0x{%"PRIx32",%"PRIx32"})",
|
||||
traversal->mdir.rbyd.block, traversal->mdir.other_block);
|
||||
return LFS_ERR_CORRUPT;
|
||||
}
|
||||
if (traversal->tortoise_step
|
||||
== ((lfs_size_t)1 << traversal->tortoise_power)) {
|
||||
traversal->tortoise_mpair = lfsr_mdir_mpair(&traversal->mdir);
|
||||
traversal->tortoise_step = 0;
|
||||
traversal->tortoise_power += 1;
|
||||
}
|
||||
traversal->tortoise_step += 1;
|
||||
}
|
||||
|
||||
// new traversal? start with 0x{0,1}
|
||||
//
|
||||
// note we make sure to include fake mroots!
|
||||
@@ -5578,7 +5560,7 @@ static int lfsr_mtree_traversal_next(lfs_t *lfs,
|
||||
if (data_) {
|
||||
*data_ = LFSR_DATA_BUF(&traversal->mdir, sizeof(lfsr_mdir_t));
|
||||
}
|
||||
return 0;
|
||||
goto cycle_detect;
|
||||
|
||||
// check for mroot/mtree/mdir
|
||||
} else if (traversal->mdir.mid == -1) {
|
||||
@@ -5613,7 +5595,7 @@ static int lfsr_mtree_traversal_next(lfs_t *lfs,
|
||||
if (data_) {
|
||||
*data_ = LFSR_DATA_BUF(&traversal->mdir, sizeof(lfsr_mdir_t));
|
||||
}
|
||||
return 0;
|
||||
goto cycle_detect;
|
||||
|
||||
// no more mroots, which makes this our real mroot
|
||||
} else {
|
||||
@@ -5747,12 +5729,36 @@ static int lfsr_mtree_traversal_next(lfs_t *lfs,
|
||||
if (data_) {
|
||||
*data_ = LFSR_DATA_BUF(&traversal->mdir, sizeof(lfsr_mdir_t));
|
||||
}
|
||||
return 0;
|
||||
goto cycle_detect;
|
||||
|
||||
} else {
|
||||
LFS_ERROR("Weird mtree entry? (0x%"PRIx32")", tag);
|
||||
return LFS_ERR_CORRUPT;
|
||||
}
|
||||
|
||||
cycle_detect:;
|
||||
// detect cycles with Brent's algorithm
|
||||
//
|
||||
// note we only consider mdirs here, the btree inner nodes
|
||||
// require checksums of their pointers, so creating a valid
|
||||
// cycle is actually quite difficult
|
||||
//
|
||||
if (lfsr_mpair_eq(lfsr_mdir_mpair(&traversal->mdir),
|
||||
traversal->tortoise_mpair)) {
|
||||
LFS_ERROR("Cycle detected during mtree traversal "
|
||||
"(0x{%"PRIx32",%"PRIx32"})",
|
||||
traversal->mdir.rbyd.block, traversal->mdir.other_block);
|
||||
return LFS_ERR_CORRUPT;
|
||||
}
|
||||
if (traversal->tortoise_step
|
||||
== ((lfs_size_t)1 << traversal->tortoise_power)) {
|
||||
traversal->tortoise_mpair = lfsr_mdir_mpair(&traversal->mdir);
|
||||
traversal->tortoise_step = 0;
|
||||
traversal->tortoise_power += 1;
|
||||
}
|
||||
traversal->tortoise_step += 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user