btree: Dropped reliance on leaf cache during traversals

Brings back lfs3_btrv_t, but keeps some of the btree internal changes.

I think the biggest one is dropping the internal branch pointer, now
instead of internally pointing to the root rbyd, we just unconditionally
sync the rbyd state anytime the rbyd matches the root's weight. This is
necessary to avoid out-of-sync state when traversing bshrubs under
mutation.

Also after refactoring I think the current btree traversal logic is
easier to read.

---

This is in preparation for removing the leaf cache, or at least making
it opt-in.

It adds a chunk of stack, but in theory we can reclaim this by allowing
leaf caches to be disabled:

           code          stack          ctx
  before: 37160           2352          688
  after:  37088 (-0.2%)   2384 (+1.4%)  688 (+0.0%)
This commit is contained in:
Christopher Haster
2025-10-25 16:48:04 -05:00
parent 5d905e6da4
commit 39a265ce90
4 changed files with 103 additions and 85 deletions
+48 -60
View File
@@ -6428,29 +6428,35 @@ static lfs3_scmp_t lfs3_btree_namelookup(lfs3_t *lfs3, lfs3_btree_t *btree,
// incremental btree traversal
//
// when bid is initialized to -1, incrementally traverses all btree
// nodes using the leaf rbyd to keep track of state
//
// unlike lfs3_btree_lookupnext, this includes inner btree nodes
//
// just don't call lfs3_btree_lookupnext/lookup/commit or anything else
// that uses the leaf rbyd mid-traversal or things will break!
static void lfs3_btrv_init(lfs3_btrv_t *btrv) {
btrv->bid = -1;
}
static lfs3_stag_t lfs3_btree_traverse(lfs3_t *lfs3, lfs3_btree_t *btree,
lfs3_sbid_t bid,
lfs3_btrv_t *btrv,
lfs3_sbid_t *bid_, lfs3_bid_t *weight_, lfs3_data_t *data_) {
// restart from the root?
if (bid == -1 || btree->leaf.r.weight == 0) {
if (btrv->bid == -1
|| btrv->rid >= (lfs3_srid_t)btrv->rbyd.weight
// we do this unconditionally when rbyd == root to avoid
// bshrubs falling out-of-sync
|| btrv->rbyd.weight == btree->r.weight) {
// end of traversal?
if (bid >= (lfs3_sbid_t)btree->r.weight) {
if (btrv->bid >= (lfs3_sbid_t)btree->r.weight) {
return LFS3_ERR_NOENT;
}
// restart from the root
btree->leaf.bid = btree->r.weight-1;
btree->leaf.r = btree->r;
btrv->rbyd = btree->r;
btrv->rid = btrv->bid;
// explicitly traverse the root even if weight=0
if (bid == -1) {
if (btrv->bid == -1) {
btrv->bid += 1;
btrv->rid += 1;
// unless we don't even have a root yet
if (lfs3_rbyd_trunk(&btree->r) != 0
// or are a shrub
@@ -6469,29 +6475,16 @@ static lfs3_stag_t lfs3_btree_traverse(lfs3_t *lfs3, lfs3_btree_t *btree,
}
return LFS3_TAG_BRANCH;
}
bid = btree->leaf.bid+1;
}
}
// did someone mess with the leaf rbyd?
LFS3_ASSERT(bid >= (lfs3_sbid_t)(btree->leaf.bid-(btree->leaf.r.weight-1))
&& bid <= (lfs3_sbid_t)(btree->leaf.bid+1));
// the user increments bid to move the traversal forward, but if we
// were at a btree inner node we remap this to descending down the
// tree
if (bid == (lfs3_sbid_t)(btree->leaf.bid+1)) {
bid = btree->leaf.bid-(btree->leaf.r.weight-1);
}
// descend down the tree
while (true) {
lfs3_srid_t rid__;
lfs3_rid_t weight__;
lfs3_data_t data__;
lfs3_stag_t tag__ = lfs3_rbyd_lookupnext(lfs3, &btree->leaf.r,
bid - (btree->leaf.bid-(btree->leaf.r.weight-1)), 0,
lfs3_stag_t tag__ = lfs3_rbyd_lookupnext(lfs3, &btrv->rbyd,
btrv->rid, 0,
&rid__, &weight__, &data__);
if (tag__ < 0) {
return tag__;
@@ -6499,7 +6492,7 @@ static lfs3_stag_t lfs3_btree_traverse(lfs3_t *lfs3, lfs3_btree_t *btree,
// if we found a bname, lookup the branch
if (tag__ == LFS3_TAG_BNAME) {
tag__ = lfs3_rbyd_lookup(lfs3, &btree->leaf.r,
tag__ = lfs3_rbyd_lookup(lfs3, &btrv->rbyd,
rid__, LFS3_TAG_BRANCH,
&data__);
if (tag__ < 0) {
@@ -6508,48 +6501,42 @@ static lfs3_stag_t lfs3_btree_traverse(lfs3_t *lfs3, lfs3_btree_t *btree,
}
}
// adjust bid__ with subtree's weight
lfs3_bid_t bid__ = (btree->leaf.bid-(btree->leaf.r.weight-1)) + rid__;
// found another branch
if (tag__ == LFS3_TAG_BRANCH) {
// adjust rid with subtree's weight
btrv->rid -= (rid__ - (weight__-1));
// fetch the next branch
lfs3_rbyd_t rbyd__;
int err = lfs3_data_fetchbranch(lfs3, &data__, weight__,
&rbyd__);
&btrv->rbyd);
if (err) {
return err;
}
btree->leaf.bid = bid__;
btree->leaf.r = rbyd__;
// return inner btree nodes if this is the first time we've
// seen them
if (bid - (btree->leaf.bid-(btree->leaf.r.weight-1)) == 0) {
if (btrv->rid == 0) {
if (bid_) {
*bid_ = btree->leaf.bid;
*bid_ = btrv->bid + (rid__ - btrv->rid);
}
if (weight_) {
*weight_ = btree->leaf.r.weight;
*weight_ = weight__;
}
if (data_) {
data_->u.buffer = (const uint8_t*)&btree->leaf.r;
data_->u.buffer = (const uint8_t*)&btrv->rbyd;
}
return LFS3_TAG_BRANCH;
}
// found our bid
} else {
// discard the leaf when we're done with it to restart from
// the root on the next call
if (rid__ == (lfs3_srid_t)(btree->leaf.r.weight-1)) {
lfs3_btree_discardleaf(btree);
}
// otherwise we let the user increment bid to step through
// the full leaf
// move on to the next rid
//
// this + leaf caching avoids redoing the full btree walk
// note this effectively traverses a full leaf without redoing
// the btree walk
lfs3_bid_t bid__ = btrv->bid + (rid__ - btrv->rid);
btrv->bid = bid__ + 1;
btrv->rid = rid__ + 1;
if (bid_) {
*bid_ = bid__;
@@ -6901,9 +6888,9 @@ static lfs3_stag_t lfs3_bshrub_lookup(lfs3_t *lfs3, lfs3_bshrub_t *bshrub,
}
static lfs3_stag_t lfs3_bshrub_traverse(lfs3_t *lfs3, lfs3_bshrub_t *bshrub,
lfs3_sbid_t bid,
lfs3_btrv_t *btrv,
lfs3_sbid_t *bid_, lfs3_bid_t *weight_, lfs3_data_t *data_) {
return lfs3_btree_traverse(lfs3, &bshrub->shrub, bid,
return lfs3_btree_traverse(lfs3, &bshrub->shrub, btrv,
bid_, weight_, data_);
}
@@ -10132,7 +10119,7 @@ static lfs3_stag_t lfs3_mtree_traverse_(lfs3_t *lfs3, lfs3_mtrv_t *mtrv,
}
// transition to traversing the mtree
mtrv->bid = -2;
lfs3_btrv_init(&mtrv->btrv);
lfs3_t_settstate(&mtrv->b.h.flags, LFS3_TSTATE_MTREE);
continue;
@@ -10156,7 +10143,7 @@ static lfs3_stag_t lfs3_mtree_traverse_(lfs3_t *lfs3, lfs3_mtrv_t *mtrv,
#ifdef LFS3_GBMAP
// transition to traversing the gbmap if there is one
mtrv->b.shrub = lfs3->gbmap.b;
mtrv->bid = -2;
lfs3_btrv_init(&mtrv->btrv);
lfs3_t_settstate(&mtrv->b.h.flags, LFS3_TSTATE_GBMAP);
continue;
#endif
@@ -10197,7 +10184,7 @@ static lfs3_stag_t lfs3_mtree_traverse_(lfs3_t *lfs3, lfs3_mtrv_t *mtrv,
// here, lfs3_bshrub_fetch ignores these for us
if (err != LFS3_ERR_NOENT) {
// start traversing
mtrv->bid = -2;
lfs3_btrv_init(&mtrv->btrv);
lfs3_t_settstate(&mtrv->b.h.flags, LFS3_TSTATE_BTREE);
continue;
@@ -10240,7 +10227,7 @@ static lfs3_stag_t lfs3_mtree_traverse_(lfs3_t *lfs3, lfs3_mtrv_t *mtrv,
// transition to traversing the file
const lfs3_file_t *file = (const lfs3_file_t*)mtrv->h;
mtrv->b.shrub = file->b.shrub;
mtrv->bid = -2;
lfs3_btrv_init(&mtrv->btrv);
lfs3_t_settstate(&mtrv->b.h.flags, LFS3_TSTATE_HBTREE);
// wait, do we have an ungrafted leaf?
@@ -10259,8 +10246,8 @@ static lfs3_stag_t lfs3_mtree_traverse_(lfs3_t *lfs3, lfs3_mtrv_t *mtrv,
case LFS3_TSTATE_GBMAP:;
case LFS3_TSTATE_GBMAP_P:;
// traverse through our bshrub/btree
tag = lfs3_bshrub_traverse(lfs3, &mtrv->b, mtrv->bid+1,
&mtrv->bid, NULL, &data);
tag = lfs3_bshrub_traverse(lfs3, &mtrv->b, &mtrv->btrv,
NULL, NULL, &data);
if (tag < 0) {
if (tag == LFS3_ERR_NOENT) {
// clear the bshrub state
@@ -10300,7 +10287,7 @@ static lfs3_stag_t lfs3_mtree_traverse_(lfs3_t *lfs3, lfs3_mtrv_t *mtrv,
&lfs3->gbmap.b_p,
&lfs3->gbmap.b) != 0) {
mtrv->b.shrub = lfs3->gbmap.b_p;
mtrv->bid = -2;
lfs3_btrv_init(&mtrv->btrv);
lfs3_t_settstate(&mtrv->b.h.flags,
LFS3_TSTATE_GBMAP_P);
continue;
@@ -14861,11 +14848,12 @@ static int lfs3_file_ck(lfs3_t *lfs3, lfs3_file_t *file, uint32_t flags) {
}
// traverse the file's bshrub/btree
lfs3_sbid_t bid = -2;
lfs3_btrv_t btrv;
lfs3_btrv_init(&btrv);
while (true) {
lfs3_data_t data;
lfs3_stag_t tag = lfs3_bshrub_traverse(lfs3, &file->b, bid+1,
&bid, NULL, &data);
lfs3_stag_t tag = lfs3_bshrub_traverse(lfs3, &file->b, &btrv,
NULL, NULL, &data);
if (tag < 0) {
if (tag == LFS3_ERR_NOENT) {
break;
+7 -1
View File
@@ -850,6 +850,12 @@ typedef struct lfs3_dir {
} lfs3_dir_t;
// littlefs traversal type
typedef struct lfs3_btrv {
lfs3_sbid_t bid;
lfs3_rbyd_t rbyd;
lfs3_srid_t rid;
} lfs3_btrv_t;
typedef struct lfs3_mtrv {
// mdir/bshrub/btree state, this also includes our traversal
// state machine and cycle detection state
@@ -857,7 +863,7 @@ typedef struct lfs3_mtrv {
// opened file state
lfs3_handle_t *h;
// bshrub/btree traversal state
lfs3_sbid_t bid;
lfs3_btrv_t btrv;
// recalculate gcksum when traversing with ckmeta
uint32_t gcksum;
+8 -4
View File
@@ -4284,15 +4284,17 @@ code = '''
uint8_t *seen = malloc((BLOCK_COUNT+7)/8);
memset(seen, 0, (BLOCK_COUNT+7)/8);
lfs3_sbid_t bid = -2;
lfs3_btrv_t btrv;
lfs3_btrv_init(&btrv);
for (lfs3_block_t i = 0;; i++) {
// a bit hacky, but this catches infinite loops
assert(i <= 2*N);
lfs3_stag_t tag;
lfs3_sbid_t bid;
lfs3_bid_t weight;
lfs3_data_t data;
tag = lfs3_btree_traverse(&lfs3, &btree, bid+1,
tag = lfs3_btree_traverse(&lfs3, &btree, &btrv,
&bid, &weight, &data);
assert(tag >= 0 || tag == LFS3_ERR_NOENT);
if (tag == LFS3_ERR_NOENT) {
@@ -4440,15 +4442,17 @@ code = '''
uint8_t *seen = malloc((BLOCK_COUNT+7)/8);
memset(seen, 0, (BLOCK_COUNT+7)/8);
lfs3_sbid_t bid = -2;
lfs3_btrv_t btrv;
lfs3_btrv_init(&btrv);
for (lfs3_block_t i = 0;; i++) {
// a bit hacky, but this catches infinite loops
assert(i <= 2*N);
lfs3_stag_t tag;
lfs3_sbid_t bid;
lfs3_bid_t weight;
lfs3_data_t data;
tag = lfs3_btree_traverse(&lfs3, &btree, bid+1,
tag = lfs3_btree_traverse(&lfs3, &btree, &btrv,
&bid, &weight, &data);
assert(tag >= 0 || tag == LFS3_ERR_NOENT);
if (tag == LFS3_ERR_NOENT) {
+40 -20
View File
@@ -183,15 +183,17 @@ code = '''
lfs3_size_t fragments = 0;
lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0;
lfs3_sbid_t bid = -2;
lfs3_btrv_t btrv;
lfs3_btrv_init(&btrv);
for (lfs3_block_t i = 0;; i++) {
// a bit hacky, but this catches infinite loops
assert(i < 2*BLOCK_COUNT);
lfs3_stag_t tag;
lfs3_sbid_t bid;
lfs3_bid_t weight;
lfs3_data_t data;
tag = lfs3_bshrub_traverse(&lfs3, &file.b, bid+1,
tag = lfs3_bshrub_traverse(&lfs3, &file.b, &btrv,
&bid, &weight, &data);
assert(tag >= 0 || tag == LFS3_ERR_NOENT);
if (tag == LFS3_ERR_NOENT) {
@@ -329,15 +331,17 @@ code = '''
lfs3_block_t blocks = 0;
lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0;
lfs3_sbid_t bid = -2;
lfs3_btrv_t btrv;
lfs3_btrv_init(&btrv);
for (lfs3_block_t i = 0;; i++) {
// a bit hacky, but this catches infinite loops
assert(i < 2*BLOCK_COUNT);
lfs3_stag_t tag;
lfs3_sbid_t bid;
lfs3_bid_t weight;
lfs3_data_t data;
tag = lfs3_bshrub_traverse(&lfs3, &file.b, bid+1,
tag = lfs3_bshrub_traverse(&lfs3, &file.b, &btrv,
&bid, &weight, &data);
assert(tag >= 0 || tag == LFS3_ERR_NOENT);
if (tag == LFS3_ERR_NOENT) {
@@ -593,15 +597,17 @@ code = '''
lfs3_size_t fragments = 0;
lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0;
lfs3_sbid_t bid = -2;
lfs3_btrv_t btrv;
lfs3_btrv_init(&btrv);
for (lfs3_block_t i = 0;; i++) {
// a bit hacky, but this catches infinite loops
assert(i < 2*BLOCK_COUNT);
lfs3_stag_t tag;
lfs3_sbid_t bid;
lfs3_bid_t weight;
lfs3_data_t data;
tag = lfs3_bshrub_traverse(&lfs3, &file.b, bid+1,
tag = lfs3_bshrub_traverse(&lfs3, &file.b, &btrv,
&bid, &weight, &data);
assert(tag >= 0 || tag == LFS3_ERR_NOENT);
if (tag == LFS3_ERR_NOENT) {
@@ -754,15 +760,17 @@ code = '''
lfs3_block_t blocks = 0;
lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0;
lfs3_sbid_t bid = -2;
lfs3_btrv_t btrv;
lfs3_btrv_init(&btrv);
for (lfs3_block_t i = 0;; i++) {
// a bit hacky, but this catches infinite loops
assert(i < 2*BLOCK_COUNT);
lfs3_stag_t tag;
lfs3_sbid_t bid;
lfs3_bid_t weight;
lfs3_data_t data;
tag = lfs3_bshrub_traverse(&lfs3, &file.b, bid+1,
tag = lfs3_bshrub_traverse(&lfs3, &file.b, &btrv,
&bid, &weight, &data);
assert(tag >= 0 || tag == LFS3_ERR_NOENT);
if (tag == LFS3_ERR_NOENT) {
@@ -2080,15 +2088,17 @@ code = '''
lfs3_size_t fragments = 0;
lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0;
lfs3_sbid_t bid = -2;
lfs3_btrv_t btrv;
lfs3_btrv_init(&btrv);
for (lfs3_block_t i = 0;; i++) {
// a bit hacky, but this catches infinite loops
assert(i < 2*BLOCK_COUNT);
lfs3_stag_t tag;
lfs3_sbid_t bid;
lfs3_bid_t weight;
lfs3_data_t data;
tag = lfs3_bshrub_traverse(&lfs3, &file.b, bid+1,
tag = lfs3_bshrub_traverse(&lfs3, &file.b, &btrv,
&bid, &weight, &data);
assert(tag >= 0 || tag == LFS3_ERR_NOENT);
if (tag == LFS3_ERR_NOENT) {
@@ -2230,15 +2240,17 @@ code = '''
lfs3_size_t fragments = 0;
lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0;
lfs3_sbid_t bid = -2;
lfs3_btrv_t btrv;
lfs3_btrv_init(&btrv);
for (lfs3_block_t i = 0;; i++) {
// a bit hacky, but this catches infinite loops
assert(i < 2*BLOCK_COUNT);
lfs3_stag_t tag;
lfs3_sbid_t bid;
lfs3_bid_t weight;
lfs3_data_t data;
tag = lfs3_bshrub_traverse(&lfs3, &file.b, bid+1,
tag = lfs3_bshrub_traverse(&lfs3, &file.b, &btrv,
&bid, &weight, &data);
assert(tag >= 0 || tag == LFS3_ERR_NOENT);
if (tag == LFS3_ERR_NOENT) {
@@ -2527,15 +2539,17 @@ code = '''
lfs3_size_t fragments = 0;
lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0;
lfs3_sbid_t bid = -2;
lfs3_btrv_t btrv;
lfs3_btrv_init(&btrv);
for (lfs3_block_t i = 0;; i++) {
// a bit hacky, but this catches infinite loops
assert(i < 2*BLOCK_COUNT);
lfs3_stag_t tag;
lfs3_sbid_t bid;
lfs3_bid_t weight;
lfs3_data_t data;
tag = lfs3_bshrub_traverse(&lfs3, &file.b, bid+1,
tag = lfs3_bshrub_traverse(&lfs3, &file.b, &btrv,
&bid, &weight, &data);
assert(tag >= 0 || tag == LFS3_ERR_NOENT);
if (tag == LFS3_ERR_NOENT) {
@@ -2692,15 +2706,17 @@ code = '''
lfs3_block_t blocks = 0;
lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0;
lfs3_sbid_t bid = -2;
lfs3_btrv_t btrv;
lfs3_btrv_init(&btrv);
for (lfs3_block_t i = 0;; i++) {
// a bit hacky, but this catches infinite loops
assert(i < 2*BLOCK_COUNT);
lfs3_stag_t tag;
lfs3_sbid_t bid;
lfs3_bid_t weight;
lfs3_data_t data;
tag = lfs3_bshrub_traverse(&lfs3, &file.b, bid+1,
tag = lfs3_bshrub_traverse(&lfs3, &file.b, &btrv,
&bid, &weight, &data);
assert(tag >= 0 || tag == LFS3_ERR_NOENT);
if (tag == LFS3_ERR_NOENT) {
@@ -2983,15 +2999,17 @@ code = '''
lfs3_size_t fragments = 0;
lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0;
lfs3_sbid_t bid = -2;
lfs3_btrv_t btrv;
lfs3_btrv_init(&btrv);
for (lfs3_block_t i = 0;; i++) {
// a bit hacky, but this catches infinite loops
assert(i < 2*BLOCK_COUNT);
lfs3_stag_t tag;
lfs3_sbid_t bid;
lfs3_bid_t weight;
lfs3_data_t data;
tag = lfs3_bshrub_traverse(&lfs3, &file.b, bid+1,
tag = lfs3_bshrub_traverse(&lfs3, &file.b, &btrv,
&bid, &weight, &data);
assert(tag >= 0 || tag == LFS3_ERR_NOENT);
if (tag == LFS3_ERR_NOENT) {
@@ -3152,15 +3170,17 @@ code = '''
lfs3_block_t blocks = 0;
lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0;
lfs3_sbid_t bid = -2;
lfs3_btrv_t btrv;
lfs3_btrv_init(&btrv);
for (lfs3_block_t i = 0;; i++) {
// a bit hacky, but this catches infinite loops
assert(i < 2*BLOCK_COUNT);
lfs3_stag_t tag;
lfs3_sbid_t bid;
lfs3_bid_t weight;
lfs3_data_t data;
tag = lfs3_bshrub_traverse(&lfs3, &file.b, bid+1,
tag = lfs3_bshrub_traverse(&lfs3, &file.b, &btrv,
&bid, &weight, &data);
assert(tag >= 0 || tag == LFS3_ERR_NOENT);
if (tag == LFS3_ERR_NOENT) {