gbmap: Reuse old gbmap during rebuilds

This changes the gbmap rebuild strategy to clear in-use ranges from a
snapshot of the old gbmap instead of building a new gbmap from scratch.

The theory of building a new gbmap from scratch is it skips the cost of
clearing in-use ranges, but:

1. This potentially misses out on erased-state still in the gbmap.

2. We would need to copy over any erased/bad state (not yet implemented)
   before traversing, and reusing the old gbmap makes this a bit
   simpler.

To make this a little bit more efficient, I extended lfs3_gbmap_set_ to
accept a weight, however this is limited to modifying only a single
range. Cross-range sets would be quite a bit more complicated (see file
grafting).

We're probably dominated by the per-block set operation during traversal
anyways.

---

Costs a bit of code, but in theory makes erased/bad block tracking
cheaper:

                 code          stack          ctx
  before:       37172           2352          684
  after:        37172 (+0.0%)   2352 (+0.0%)  684 (+0.0%)

                 code          stack          ctx
  gbmap before: 38852           2456          800
  gbmap after:  38992 (+0.4%)   2456 (+0.0%)  800 (+0.0%)
This commit is contained in:
Christopher Haster
2025-10-07 12:57:39 -05:00
parent cb9bda5a94
commit 633cbe8fd6
+60 -28
View File
@@ -10768,7 +10768,7 @@ static int lfs3_data_readgbmap(lfs3_t *lfs3, lfs3_data_t *data,
// on-disk global block-map operations // on-disk global block-map operations
#ifdef LFS3_GBMAP #ifdef LFS3_GBMAP
static int lfs3_gbmap_lookupnext(lfs3_t *lfs3, lfs3_btree_t *gbmap, static lfs3_stag_t lfs3_gbmap_lookupnext(lfs3_t *lfs3, lfs3_btree_t *gbmap,
lfs3_bid_t bid, lfs3_bid_t bid,
lfs3_bid_t *bid_, lfs3_bid_t *weight_) { lfs3_bid_t *bid_, lfs3_bid_t *weight_) {
return lfs3_btree_lookupnext(lfs3, gbmap, bid, return lfs3_btree_lookupnext(lfs3, gbmap, bid,
@@ -10804,8 +10804,13 @@ failed:;
#endif #endif
#if !defined(LFS3_RDONLY) && defined(LFS3_GBMAP) #if !defined(LFS3_RDONLY) && defined(LFS3_GBMAP)
static int lfs3_gbmap_set(lfs3_t *lfs3, lfs3_btree_t *gbmap, // note while this does takes a weight, it's limited to only a single
lfs3_block_t block, lfs3_tag_t tag) { // range, cross-range sets are not currently not supported
//
// really this just provides a shortcut for bulk clearing ranges in
// lfs3_alloc_rebuildgbmap
static int lfs3_gbmap_set_(lfs3_t *lfs3, lfs3_btree_t *gbmap,
lfs3_block_t block, lfs3_block_t weight, lfs3_tag_t tag) {
// lookup gbmap range // lookup gbmap range
lfs3_bid_t bid__; lfs3_bid_t bid__;
lfs3_bid_t weight__; lfs3_bid_t weight__;
@@ -10837,9 +10842,9 @@ static int lfs3_gbmap_set(lfs3_t *lfs3, lfs3_btree_t *gbmap,
// note this is never unnecessary work, the resulting neighbors // note this is never unnecessary work, the resulting neighbors
// can't share a type or else we would've already returned // can't share a type or else we would've already returned
int err = lfs3_gbmap_commit(lfs3, &gbmap_, bid__, LFS3_RATTRS( int err = lfs3_gbmap_commit(lfs3, &gbmap_, bid__, LFS3_RATTRS(
(bid__-(weight__-1) < block) (bid__-(weight__-1) < block-(weight-1))
? LFS3_RATTR(LFS3_TAG_GROW, -((bid__+1) - block)) ? LFS3_RATTR(LFS3_TAG_GROW, -((bid__+1) - (block-(weight-1))))
: LFS3_RATTR(LFS3_TAG_RM, -((bid__+1) - block)), : LFS3_RATTR(LFS3_TAG_RM, -((bid__+1) - (block-(weight-1)))),
(bid__ > block) (bid__ > block)
? LFS3_RATTR(tag__, +(bid__ - block)) ? LFS3_RATTR(tag__, +(bid__ - block))
: LFS3_RATTR_NOOP())); : LFS3_RATTR_NOOP()));
@@ -10848,7 +10853,7 @@ static int lfs3_gbmap_set(lfs3_t *lfs3, lfs3_btree_t *gbmap,
} }
// weight of new range // weight of new range
lfs3_bid_t weight = 1; lfs3_bid_t weight_ = weight;
// can we merge with right neighbor? // can we merge with right neighbor?
if (block < lfs3->block_count-1) { if (block < lfs3->block_count-1) {
@@ -10863,12 +10868,12 @@ static int lfs3_gbmap_set(lfs3_t *lfs3, lfs3_btree_t *gbmap,
if (tag__ == tag) { if (tag__ == tag) {
LFS3_ASSERT(weight__ == bid__ - block); LFS3_ASSERT(weight__ == bid__ - block);
// merge // merge
weight += weight__; weight_ += weight__;
// delete to prepare merge // delete to prepare merge
// //
// note the shifted bid because of the previous delete // note the shifted bid because of the previous delete
err = lfs3_gbmap_commit(lfs3, &gbmap_, bid__-1, LFS3_RATTRS( err = lfs3_gbmap_commit(lfs3, &gbmap_, bid__-weight, LFS3_RATTRS(
LFS3_RATTR(LFS3_TAG_RM, -weight__))); LFS3_RATTR(LFS3_TAG_RM, -weight__)));
if (err) { if (err) {
return err; return err;
@@ -10877,9 +10882,9 @@ static int lfs3_gbmap_set(lfs3_t *lfs3, lfs3_btree_t *gbmap,
} }
// can we merge with left neighbor? // can we merge with left neighbor?
if (block > 0) { if (block-(weight-1) > 0) {
// note the use of the old gbmap to try to leverage leaf caching // note the use of the old gbmap to try to leverage leaf caching
tag__ = lfs3_gbmap_lookupnext(lfs3, gbmap, block-1, tag__ = lfs3_gbmap_lookupnext(lfs3, gbmap, block-weight,
&bid__, &weight__); &bid__, &weight__);
if (tag__ < 0) { if (tag__ < 0) {
LFS3_ASSERT(tag__ != LFS3_ERR_NOENT); LFS3_ASSERT(tag__ != LFS3_ERR_NOENT);
@@ -10887,10 +10892,10 @@ static int lfs3_gbmap_set(lfs3_t *lfs3, lfs3_btree_t *gbmap,
} }
if (tag__ == tag) { if (tag__ == tag) {
LFS3_ASSERT(bid__ == block-1); LFS3_ASSERT(bid__ == block-weight);
// we can merge everything in one commit here // we can merge everything in one commit here
err = lfs3_gbmap_commit(lfs3, &gbmap_, bid__, LFS3_RATTRS( err = lfs3_gbmap_commit(lfs3, &gbmap_, bid__, LFS3_RATTRS(
LFS3_RATTR(LFS3_TAG_GROW, +weight))); LFS3_RATTR(LFS3_TAG_GROW, +weight_)));
if (err) { if (err) {
return err; return err;
} }
@@ -10902,8 +10907,8 @@ static int lfs3_gbmap_set(lfs3_t *lfs3, lfs3_btree_t *gbmap,
} }
// needs a new range // needs a new range
err = lfs3_gbmap_commit(lfs3, &gbmap_, block, LFS3_RATTRS( err = lfs3_gbmap_commit(lfs3, &gbmap_, block-(weight-1), LFS3_RATTRS(
LFS3_RATTR(tag, +weight))); LFS3_RATTR(tag, +weight_)));
if (err) { if (err) {
return err; return err;
} }
@@ -10914,6 +10919,13 @@ static int lfs3_gbmap_set(lfs3_t *lfs3, lfs3_btree_t *gbmap,
} }
#endif #endif
#if !defined(LFS3_RDONLY) && defined(LFS3_GBMAP)
static int lfs3_gbmap_set(lfs3_t *lfs3, lfs3_btree_t *gbmap,
lfs3_block_t block, lfs3_tag_t tag) {
return lfs3_gbmap_set_(lfs3, gbmap, block, 1, tag);
}
#endif
#if !defined(LFS3_RDONLY) && defined(LFS3_GBMAP) #if !defined(LFS3_RDONLY) && defined(LFS3_GBMAP)
static int lfs3_gbmap_setbptr(lfs3_t *lfs3, lfs3_btree_t *gbmap, static int lfs3_gbmap_setbptr(lfs3_t *lfs3, lfs3_btree_t *gbmap,
lfs3_tag_t tag, const lfs3_bptr_t *bptr, lfs3_tag_t tag, const lfs3_bptr_t *bptr,
@@ -11276,23 +11288,42 @@ static int lfs3_alloc_rebuildgbmap(lfs3_t *lfs3) {
lfs3->lookahead.gbmapped, lfs3->lookahead.gbmapped,
lfs3->cfg->block_count); lfs3->cfg->block_count);
// create a new gbmap // create a copy of the gbmap
lfs3_btree_t gbmap_; lfs3_btree_t gbmap_ = lfs3->gbmap.b;
lfs3_btree_init(&gbmap_); // TODO should we just claim all matching btrees in lfs3_btree_commit?
// mark as unfetched in case of error
lfs3_btree_claim(&lfs3->gbmap.b);
int err = lfs3_gbmap_commit(lfs3, &gbmap_, 0, LFS3_RATTRS( // mark any in-use blocks as free
LFS3_RATTR(LFS3_TAG_BMFREE, +lfs3->cfg->block_count))); //
if (err) { // we do this instead of creating a new gbmap to (1) preserve any
goto failed; // erased/bad info and (2) try to best use any available
// erased-state
lfs3_block_t block = -1;
int err;
while (true) {
lfs3_block_t weight;
lfs3_stag_t tag = lfs3_gbmap_lookupnext(lfs3, &gbmap_, block+1,
&block, &weight);
if (tag < 0) {
if (tag == LFS3_ERR_NOENT) {
break;
}
err = tag;
goto failed;
}
if (tag == LFS3_TAG_BMINUSE) {
err = lfs3_gbmap_set_(lfs3, &gbmap_, block, weight,
LFS3_TAG_BMFREE);
if (err) {
goto failed;
}
}
} }
// traverse the filesystem, building up knowledge of what blocks are // traverse the filesystem, building up knowledge of what blocks are
// in-use // in-use
//
// TODO should we also copy over bad/erased blocks from the old gbmap?
// TODO should we just copy the old gbmap and manually clear in-use
// blocks? we're already doing a O(n) scan of the filesystem anyways
//
lfs3_trv_t trv; lfs3_trv_t trv;
lfs3_trv_init(&trv, LFS3_T_RDONLY | LFS3_T_LOOKAHEAD); lfs3_trv_init(&trv, LFS3_T_RDONLY | LFS3_T_LOOKAHEAD);
while (true) { while (true) {
@@ -11303,7 +11334,8 @@ static int lfs3_alloc_rebuildgbmap(lfs3_t *lfs3) {
if (tag == LFS3_ERR_NOENT) { if (tag == LFS3_ERR_NOENT) {
break; break;
} }
return tag; err = tag;
goto failed;
} }
// track in-use blocks // track in-use blocks