Reimplemented eager shrub eviction, now with a more reliable heuristic

Unfortunately, waiting to evict shrubs until mdir compaction does not
work because we only have a single pcache. When we evict a bshrub we
need a pcache for writing the new btree root, but if we do this during
mdir compaction, our pcache is already busy handling the mdir
compaction. We can't do a separate pass for bshrub eviction, since this
would require tracking an unbounded number of new btree roots.

In the previous shrub design, we meticulously tracked the compacted
shrub estimate in RAM, determining exactly how the estimate would change
as a part of shrub carve operations.

This worked, but was fragile. It was easy for the shrub estimate to
diverge from the actual value, and required quite a bit of extra code to
maintain. Since the use cases for bshrubs is growing a bit, I didn't
want to return to this design.

So here's a new approach based on emulating btree compacts/splits inside
the shrubs:

1. When a bshrub is fetched, scan the bshrub and calculate a compaction
   estimate. Store this.

2. On every commit, find the upper bound of new data being progged, and
   keep track of estimate + progged. We can at least get this relatively
   easily from commit attr lists. We can't get the amount deleted, which
   is the problem.

3. When estimate + progged exceeds shrub_size, scan the bshrub again and
   recalculate the estimate.

4. If estimate exceeds the shrub_size/2, evict the bshrub, converting it
   into a btree.

As you may note, this is very close to how our btree compacts/splits
work, but emulated. In particular, evictions/splits occur at
(shrub_size/block_size)/2 in order to avoid runaway costs when the
bshrub/btree gets close to full.

Benefits:

- This eviction heuristic is very robust. Calculating the amount progged
  from the attr list is relatively cheap and easy, and any divergence
  should be fixed when we recalculate the estimate.

- The runtime cost is relatively small, amortized O(log n) which is
  the existing runtime to commit to rbyds.

Downsides:

- Just like btree splits, evictions force our bshrub to be ~1/2 full on
  average. This combined with the 2x cost for mdir pairs, the 2x cost
  for mdirs being ~1/2 full on average, and the need for both a synced
  and unsynced copy of file bshrubs brings our file bshrub's overhead up
  to ~16x, which is getting quite high...

Anyways, bshrubs now work, and the new file topology is passing testing.

An unfortunate surprise is the jump in stack cost. This seems to come from
moving the lfsr_btree_flush logic into the hot-path that includes bshrub
commit + mdir commit + all the mtree logic. Previously the separate of
btree/shrub commits meant that the more complex block/btree/crystal logic
was on a separate path from the mdir commit logic:

                    code           stack           lfsr_file_t
  before bshrubs:  31840            2072                   120
  after bshrubs:   30756  (-3.5%)   2448 (+15.4%)          104 (-15.4%)

I _think_ the reality is not actually as bad as measured, most of these
flush/carve/commit functions calculate some work and then commit it in
seperate steps. In theory GCC's shrinkwrapping optimizations should
limit the stack to only what we need as we finish different
calculations, but our current stack measurement scripts just add
together the whole frames, so any per-call stack optimizations get
missed...
This commit is contained in:
Christopher Haster
2023-11-20 00:13:51 -06:00
parent 6b82e9fb25
commit 6bd00caf93
3 changed files with 308 additions and 339 deletions
+278 -311
View File
@@ -3272,6 +3272,7 @@ static int lfsr_rbyd_appendcompactattr(lfs_t *lfs, lfsr_rbyd_t *rbyd,
}
static int lfsr_rbyd_appendcompactrbyd(lfs_t *lfs, lfsr_rbyd_t *rbyd_,
bool shrub,
lfsr_srid_t start_rid, lfsr_srid_t end_rid,
const lfsr_rbyd_t *rbyd) {
// copy over tags in the rbyd in order
@@ -3297,7 +3298,8 @@ static int lfsr_rbyd_appendcompactrbyd(lfs_t *lfs, lfsr_rbyd_t *rbyd_,
}
// write the tag
err = lfsr_rbyd_appendcompactattr(lfs, rbyd_, tag, weight, data);
err = lfsr_rbyd_appendcompactattr(lfs, rbyd_,
((shrub) ? LFSR_TAG_SHRUB : 0) | tag, weight, data);
if (err) {
LFS_ASSERT(err != LFS_ERR_RANGE);
return err;
@@ -3314,9 +3316,26 @@ static int lfsr_rbyd_compact(lfs_t *lfs, lfsr_rbyd_t *rbyd,
// offset must be after the revision count
LFS_ASSERT(off >= sizeof(uint32_t));
// ignore empty rbyds, we can't really compact these, so leave it up to
// upper layers to deal with this
if (rbyd->eoff <= sizeof(uint32_t)) {
// make sure every rbyd starts with a revision count
if (rbyd->eoff == 0) {
int err = lfsr_rbyd_appendrev(lfs, rbyd, 0);
if (err) {
return err;
}
}
// empty rbyd? write a null tag so our trunk can still point to something
if (rbyd->eoff == off) {
lfs_ssize_t d = lfsr_bd_progtag(lfs, rbyd->block, rbyd->eoff,
shrub ? LFSR_TAG_SHRUB(NULL) : LFSR_TAG_NULL, 0, 0,
&rbyd->cksum);
if (d < 0) {
return d;
}
rbyd->eoff += d;
rbyd->trunk = off;
rbyd->weight = 0;
return 0;
}
@@ -4000,6 +4019,7 @@ static lfs_ssize_t lfsr_btree_commit_(lfs_t *lfs,
if (rbyd.trunk == 0 || rbyd.weight == btree->weight) {
// are we root and shrub? yield root updates to shrub commit
if (shrub) {
*btree = rbyd;
if (attrs_) {
*attrs_ = attrs;
}
@@ -4203,7 +4223,7 @@ static lfs_ssize_t lfsr_btree_commit_(lfs_t *lfs,
}
// try to compact
err = lfsr_rbyd_appendcompactrbyd(lfs, &rbyd_, -1, -1, &rbyd);
err = lfsr_rbyd_appendcompactrbyd(lfs, &rbyd_, false, -1, -1, &rbyd);
if (err) {
LFS_ASSERT(err != LFS_ERR_RANGE);
return err;
@@ -4250,7 +4270,8 @@ static lfs_ssize_t lfsr_btree_commit_(lfs_t *lfs,
}
// copy over tags < split_rid
err = lfsr_rbyd_appendcompactrbyd(lfs, &rbyd_, -1, split_rid, &rbyd);
err = lfsr_rbyd_appendcompactrbyd(lfs, &rbyd_, false,
-1, split_rid, &rbyd);
if (err) {
LFS_ASSERT(err != LFS_ERR_RANGE);
return err;
@@ -4281,7 +4302,8 @@ static lfs_ssize_t lfsr_btree_commit_(lfs_t *lfs,
}
// copy over tags >= split_rid
err = lfsr_rbyd_appendcompactrbyd(lfs, &sibling, split_rid, -1, &rbyd);
err = lfsr_rbyd_appendcompactrbyd(lfs, &sibling, false,
split_rid, -1, &rbyd);
if (err) {
LFS_ASSERT(err != LFS_ERR_RANGE);
return err;
@@ -4378,13 +4400,15 @@ static lfs_ssize_t lfsr_btree_commit_(lfs_t *lfs,
}
// merge the siblings together
err = lfsr_rbyd_appendcompactrbyd(lfs, &rbyd_, -1, -1, &rbyd);
err = lfsr_rbyd_appendcompactrbyd(lfs, &rbyd_, false,
-1, -1, &rbyd);
if (err) {
LFS_ASSERT(err != LFS_ERR_RANGE);
return err;
}
err = lfsr_rbyd_appendcompactrbyd(lfs, &rbyd_, -1, -1, &sibling);
err = lfsr_rbyd_appendcompactrbyd(lfs, &rbyd_, false,
-1, -1, &sibling);
if (err) {
LFS_ASSERT(err != LFS_ERR_RANGE);
return err;
@@ -5235,51 +5259,37 @@ static int lfsr_mdir_commit__(lfs_t *lfs, lfsr_mdir_t *mdir,
// shrub tags append a set of attributes to an unrelated trunk
// in our rbyd
} else if (attrs[i].tag == LFSR_TAG_BSHRUBCOMMIT) {
// TODO what if bshrub turns into a btree?
const lfsr_bshrubcommit_t *bshrubcommit
= (const lfsr_bshrubcommit_t*)
attrs[i].data.u.buf.buffer;
// still a bshrub?
if (bshrubcommit->bshrub->btree_.block
== mdir_.u.m.blocks[0]) {
// swap out our trunk/weight temporarily, note we're
// operating on a copy so if this fails not _too_ many
// things will get messed up
//
// it is important that these rbyds share eoff/cksum/etc
//
mdir_.u.m.trunk = bshrubcommit->bshrub->btree_.trunk;
mdir_.u.m.weight = bshrubcommit->bshrub->btree_.weight;
// swap out our trunk/weight temporarily, note we're
// operating on a copy so if this fails not _too_ many
// things will get messed up
//
// it is important that these rbyds share eoff/cksum/etc
//
mdir_.u.m.trunk = bshrubcommit->bshrub->rbyd_.trunk;
mdir_.u.m.weight = bshrubcommit->bshrub->rbyd_.weight;
// append any shrub attributes
for (lfs_size_t j = 0; j < bshrubcommit->attr_count; j++) {
int err = lfsr_rbyd_appendattr(lfs, &mdir_.u.rbyd,
bshrubcommit->attrs[j].rid,
LFSR_TAG_SHRUB | bshrubcommit->attrs[j].tag,
bshrubcommit->attrs[j].delta,
bshrubcommit->attrs[j].data);
if (err) {
return err;
}
}
// revert mdir to main trunk/weight
bshrubcommit->bshrub->btree_.trunk = mdir_.u.m.trunk;
bshrubcommit->bshrub->btree_.weight = mdir_.u.m.weight;
mdir_.u.m.trunk = mdir->u.m.trunk;
mdir_.u.m.weight = mdir->u.m.weight;
// evicted into a btree?
} else {
int err = lfsr_btree_commit(lfs,
&bshrubcommit->bshrub->btree_,
bshrubcommit->attrs, bshrubcommit->attr_count);
// append any shrub attributes
for (lfs_size_t j = 0; j < bshrubcommit->attr_count; j++) {
int err = lfsr_rbyd_appendattr(lfs, &mdir_.u.rbyd,
bshrubcommit->attrs[j].rid,
LFSR_TAG_SHRUB | bshrubcommit->attrs[j].tag,
bshrubcommit->attrs[j].delta,
bshrubcommit->attrs[j].data);
if (err) {
return err;
}
}
// revert mdir to main trunk/weight
bshrubcommit->bshrub->rbyd_.trunk = mdir_.u.m.trunk;
bshrubcommit->bshrub->rbyd_.weight = mdir_.u.m.weight;
mdir_.u.m.trunk = mdir->u.m.trunk;
mdir_.u.m.weight = mdir->u.m.weight;
// lazily encode inlined trunks in case they change underneath
// us due to mdir compactions
//
@@ -5289,36 +5299,19 @@ static int lfsr_mdir_commit__(lfs_t *lfs, lfsr_mdir_t *mdir,
lfsr_bshrub_t *bshrub
= (lfsr_bshrub_t*)attrs[i].data.u.buf.buffer;
// still a bshrub?
if (bshrub->btree_.block == mdir_.u.m.blocks[0]) {
uint8_t trunk_buf[LFSR_TRUNK_DSIZE];
int err = lfsr_rbyd_appendattr(lfs, &mdir_.u.rbyd,
rid - lfs_smax32(start_rid, 0),
lfsr_tag_mode(attrs[i].tag) | LFSR_TAG_TRUNK,
attrs[i].delta,
// TODO lfsr_data_frombshrub/readbshrub?
lfsr_data_fromtrunk(
// note we use the staged trunk here
bshrub->btree_.trunk,
bshrub->btree_.weight,
trunk_buf));
if (err) {
return err;
}
// evicted into a btree?
} else {
uint8_t btree_buf[LFSR_BTREE_DSIZE];
int err = lfsr_rbyd_appendattr(lfs, &mdir_.u.rbyd,
rid - lfs_smax32(start_rid, 0),
lfsr_tag_mode(attrs[i].tag) | LFSR_TAG_BTREE,
attrs[i].delta,
lfsr_data_frombtree(
&bshrub->btree_,
btree_buf));
if (err) {
return err;
}
uint8_t trunk_buf[LFSR_TRUNK_DSIZE];
int err = lfsr_rbyd_appendattr(lfs, &mdir_.u.rbyd,
rid - lfs_smax32(start_rid, 0),
lfsr_tag_mode(attrs[i].tag) | LFSR_TAG_TRUNK,
attrs[i].delta,
// TODO lfsr_data_frombshrub/readbshrub?
lfsr_data_fromtrunk(
// note we use the staged trunk here
bshrub->rbyd_.trunk,
bshrub->rbyd_.weight,
trunk_buf));
if (err) {
return err;
}
// write out normal tags normally
@@ -5449,117 +5442,55 @@ static int lfsr_mdir_compact__(lfs_t *lfs, lfsr_mdir_t *mdir_,
}
}
// TODO evict?
// found an inlined shrub? we need to compact the shrub as well to
// bring it along with us
} else if (tag == LFSR_TAG_TRUNK) {
lfsr_rbyd_t rbyd = mdir->u.rbyd;
lfsr_rbyd_t shrub = mdir->u.rbyd;
err = lfsr_data_readtrunk(lfs, &data,
&rbyd.trunk, (lfsr_rid_t*)&rbyd.weight);
&shrub.trunk, (lfsr_rid_t*)&shrub.weight);
if (err) {
return err;
}
// does our shrub fit?
lfs_ssize_t dsize_ = lfsr_rbyd_estimate(lfs, &rbyd, -1, -1, NULL);
if (dsize_ < 0) {
return dsize_;
// keep track of the start of our new tree
lfs_size_t off = mdir_->u.rbyd.eoff;
// compact our inlined tree
err = lfsr_rbyd_appendcompactrbyd(lfs, &mdir_->u.rbyd, true,
-1, -1, &shrub);
if (err) {
LFS_ASSERT(err != LFS_ERR_RANGE);
return err;
}
// yes? compact
if ((lfs_size_t)dsize_ <= lfs->cfg->shrub_size) {
// keep track of the start of our new tree
lfs_size_t off = mdir_->u.rbyd.eoff;
err = lfsr_rbyd_compact(lfs, &mdir_->u.rbyd, true, off);
if (err) {
LFS_ASSERT(err != LFS_ERR_RANGE);
return err;
}
// compact our inlined tree
err = lfsr_rbyd_appendcompactrbyd(lfs, &mdir_->u.rbyd, -1, -1,
&rbyd);
if (err) {
LFS_ASSERT(err != LFS_ERR_RANGE);
return err;
}
// write the new shrub tag
uint8_t trunk_buf[LFSR_TRUNK_DSIZE];
err = lfsr_rbyd_appendcompactattr(lfs, &mdir_->u.rbyd,
LFSR_TAG_TRUNK, weight, lfsr_data_fromtrunk(
mdir_->u.rbyd.trunk, mdir_->u.rbyd.weight,
trunk_buf));
if (err) {
LFS_ASSERT(err != LFS_ERR_RANGE);
return err;
}
err = lfsr_rbyd_compact(lfs, &mdir_->u.rbyd, true, off);
if (err) {
LFS_ASSERT(err != LFS_ERR_RANGE);
return err;
}
// write the new shrub tag
uint8_t trunk_buf[LFSR_TRUNK_DSIZE];
err = lfsr_rbyd_appendcompactattr(lfs, &mdir_->u.rbyd,
LFSR_TAG_TRUNK, weight, lfsr_data_fromtrunk(
mdir_->u.rbyd.trunk, mdir_->u.rbyd.weight,
trunk_buf));
if (err) {
LFS_ASSERT(err != LFS_ERR_RANGE);
return err;
}
// stage any opened shrubs with their new location so we can
// update these later if our commit is a success
for (lfsr_openedmdir_t *opened = lfs->opened[
LFS_TYPE_REG-LFS_TYPE_REG];
opened;
opened = opened->next) {
lfsr_file_t *file = (lfsr_file_t*)opened;
if (lfsr_file_isbshrub(file)
&& file->u.bshrub.btree.block == mdir->u.rbyd.block
&& file->u.bshrub.btree.trunk == rbyd.trunk) {
file->u.bshrub.btree_ = mdir_->u.rbyd;
}
}
// TODO can staging be common?
// no? need to evict
} else {
lfsr_btree_t btree;
err = lfsr_btree_alloc(lfs, &btree);
if (err) {
return err;
}
err = lfsr_rbyd_appendcompactrbyd(lfs, &btree, -1, -1,
&rbyd);
if (err) {
LFS_ASSERT(err != LFS_ERR_RANGE);
return err;
}
err = lfsr_rbyd_compact(lfs, &btree, false, sizeof(uint32_t));
if (err) {
LFS_ASSERT(err != LFS_ERR_RANGE);
return err;
}
err = lfsr_rbyd_appendcksum(lfs, &btree);
if (err) {
LFS_ASSERT(err != LFS_ERR_RANGE);
return err;
}
// write the new btree tag
uint8_t btree_buf[LFSR_BTREE_DSIZE];
err = lfsr_rbyd_appendcompactattr(lfs, &mdir_->u.rbyd,
LFSR_TAG_BTREE, weight, lfsr_data_frombtree(
&btree,
btree_buf));
if (err) {
return err;
}
// stage any opened shrubs with their new btree so we can
// update these later if our commit is a success
for (lfsr_openedmdir_t *opened = lfs->opened[
LFS_TYPE_REG-LFS_TYPE_REG];
opened;
opened = opened->next) {
lfsr_file_t *file = (lfsr_file_t*)opened;
if (lfsr_file_isbshrub(file)
&& file->u.bshrub.btree.block == mdir->u.rbyd.block
&& file->u.bshrub.btree.trunk == rbyd.trunk) {
file->u.bshrub.btree_ = btree;
}
// stage any opened shrubs with their new location so we can
// update these later if our commit is a success
for (lfsr_openedmdir_t *opened = lfs->opened[
LFS_TYPE_REG-LFS_TYPE_REG];
opened;
opened = opened->next) {
lfsr_file_t *file = (lfsr_file_t*)opened;
if (lfsr_file_isbshrub(file)
&& file->u.bshrub.rbyd.block == mdir->u.rbyd.block
&& file->u.bshrub.rbyd.trunk == shrub.trunk) {
file->u.bshrub.rbyd_ = mdir_->u.rbyd;
}
}
@@ -5617,74 +5548,30 @@ static int lfsr_mdir_compact__(lfs_t *lfs, lfsr_mdir_t *mdir_,
// inlined shrub?
} else if (lfsr_file_isbshrub(file)) {
// does our shrub fit?
lfs_ssize_t dsize_ = lfsr_rbyd_estimate(lfs,
&file->u.bshrub.btree, -1, -1, NULL);
if (dsize_ < 0) {
return dsize_;
// save our current off/trunk/weight
lfs_size_t off = mdir_->u.rbyd.eoff;
lfs_size_t trunk = mdir_->u.rbyd.trunk;
lfsr_srid_t weight = mdir_->u.rbyd.weight;
// compact our shrub
err = lfsr_rbyd_appendcompactrbyd(lfs, &mdir_->u.rbyd, true,
-1, -1,
&file->u.bshrub.rbyd);
if (err) {
LFS_ASSERT(err != LFS_ERR_RANGE);
return err;
}
// yes? compact
if ((lfs_size_t)dsize_ <= lfs->cfg->shrub_size) {
// save our current off/trunk/weight
lfs_size_t off = mdir_->u.rbyd.eoff;
lfs_size_t trunk = mdir_->u.rbyd.trunk;
lfsr_srid_t weight = mdir_->u.rbyd.weight;
// compact our shrub
err = lfsr_rbyd_appendcompactrbyd(lfs,
&mdir_->u.rbyd, -1, -1,
&file->u.bshrub.btree);
if (err) {
LFS_ASSERT(err != LFS_ERR_RANGE);
return err;
}
err = lfsr_rbyd_compact(lfs, &mdir_->u.rbyd, true, off);
if (err) {
LFS_ASSERT(err != LFS_ERR_RANGE);
return err;
}
// stage our new trunk and revert to mdir trunk/weight
file->u.bshrub.btree_.block = mdir_->u.rbyd.block;
file->u.bshrub.btree_.trunk = mdir_->u.rbyd.trunk;
file->u.bshrub.btree_.weight = mdir_->u.rbyd.weight;
mdir_->u.rbyd.trunk = trunk;
mdir_->u.rbyd.weight = weight;
// no? need to evict
} else {
lfsr_btree_t btree;
err = lfsr_btree_alloc(lfs, &btree);
if (err) {
return err;
}
err = lfsr_rbyd_appendcompactrbyd(lfs, &btree, -1, -1,
&file->u.bshrub.btree);
if (err) {
LFS_ASSERT(err != LFS_ERR_RANGE);
return err;
}
err = lfsr_rbyd_compact(lfs, &btree,
false, sizeof(uint32_t));
if (err) {
LFS_ASSERT(err != LFS_ERR_RANGE);
return err;
}
err = lfsr_rbyd_appendcksum(lfs, &btree);
if (err) {
LFS_ASSERT(err != LFS_ERR_RANGE);
return err;
}
// stage our new btree
file->u.bshrub.btree_ = btree;
err = lfsr_rbyd_compact(lfs, &mdir_->u.rbyd, true, off);
if (err) {
LFS_ASSERT(err != LFS_ERR_RANGE);
return err;
}
// stage our new trunk and revert to mdir trunk/weight
file->u.bshrub.rbyd_ = mdir_->u.rbyd;
mdir_->u.rbyd.trunk = trunk;
mdir_->u.rbyd.weight = weight;
}
}
}
@@ -5751,6 +5638,9 @@ static lfs_ssize_t lfsr_mdir_estimate_(lfs_t *lfs, const lfsr_mdir_t *mdir,
// this risks ending up O(n^2) if we have many opened files... though
// if needed this could be brought down by sorting our opened files
// by mid...
//
// TODO does it? where is there n^2?
//
for (lfsr_openedmdir_t *opened = lfs->opened[
LFS_TYPE_REG-LFS_TYPE_REG];
opened;
@@ -5769,17 +5659,11 @@ static lfs_ssize_t lfsr_mdir_estimate_(lfs_t *lfs, const lfsr_mdir_t *mdir,
// inlined shrub?
} else if (lfsr_file_isbshrub(file)) {
lfs_ssize_t dsize_ = lfsr_rbyd_estimate(lfs,
&file->u.bshrub.btree, -1, -1,
&file->u.bshrub.rbyd, -1, -1,
NULL);
if (dsize_ < 0) {
return dsize_;
}
// does our shrub estimate exceed shrub_size? assume we
// will evict
if ((lfs_size_t)dsize_ <= lfs->cfg->shrub_size) {
dsize += dsize_;
}
}
}
}
@@ -5969,7 +5853,7 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir,
if (lfsr_file_isbsprout(file)) {
file->u.bsprout.data_ = file->u.bsprout.data;
} else if (lfsr_file_isbshrub(file)) {
file->u.bshrub.btree_ = file->u.bshrub.btree;
file->u.bshrub.rbyd_ = file->u.bshrub.rbyd;
}
}
@@ -6372,8 +6256,8 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir,
}
}
err = lfsr_rbyd_compact(lfs, &mrootparent_.u.rbyd,
false, sizeof(uint32_t));
err = lfsr_rbyd_compact(lfs, &mrootparent_.u.rbyd, false,
sizeof(uint32_t));
if (err) {
LFS_ASSERT(err != LFS_ERR_RANGE);
return err;
@@ -6526,7 +6410,7 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir,
if (lfsr_file_isbsprout(file)) {
file->u.bsprout.data = file->u.bsprout.data_;
} else if (lfsr_file_isbshrub(file)) {
file->u.bshrub.btree = file->u.bshrub.btree_;
file->u.bshrub.rbyd = file->u.bshrub.rbyd_;
}
}
@@ -6799,12 +6683,12 @@ next:;
static inline bool lfsr_bshrub_isbshrub(const lfsr_mdir_t *mdir,
const lfsr_bshrub_t *bshrub) {
return mdir->u.m.blocks[0] == bshrub->btree.block;
return mdir->u.m.blocks[0] == bshrub->rbyd.block;
}
static inline bool lfsr_bshrub_isbtree(const lfsr_mdir_t *mdir,
const lfsr_bshrub_t *bshrub) {
return mdir->u.m.blocks[0] != bshrub->btree.block;
return mdir->u.m.blocks[0] != bshrub->rbyd.block;
}
static int lfsr_bshrub_lookupnext(lfs_t *lfs, const lfsr_mdir_t *mdir,
@@ -6812,7 +6696,7 @@ static int lfsr_bshrub_lookupnext(lfs_t *lfs, const lfsr_mdir_t *mdir,
lfsr_bid_t *bid_,
lfsr_tag_t *tag_, lfsr_bid_t *weight_, lfsr_data_t *data_) {
(void)mdir;
return lfsr_btree_lookupnext(lfs, &bshrub->btree, bid,
return lfsr_btree_lookupnext(lfs, &bshrub->rbyd, bid,
bid_, tag_, weight_, data_);
}
@@ -6820,7 +6704,7 @@ static int lfsr_bshrub_lookup(lfs_t *lfs, const lfsr_mdir_t *mdir,
const lfsr_bshrub_t *bshrub, lfsr_bid_t bid,
lfsr_tag_t *tag_, lfsr_bid_t *weight_, lfsr_data_t *data_) {
(void)mdir;
return lfsr_btree_lookup(lfs, &bshrub->btree, bid,
return lfsr_btree_lookup(lfs, &bshrub->rbyd, bid,
tag_, weight_, data_);
}
@@ -6833,7 +6717,7 @@ static int lfsr_bshrub_commit(lfs_t *lfs, lfsr_mdir_t *mdir,
uint8_t scratch_buf[2*LFSR_BRANCH_DSIZE];
// try to commit to the btree
int err = lfsr_btree_commit_(lfs, &bshrub->btree,
int err = lfsr_btree_commit_(lfs, &bshrub->rbyd,
lfsr_bshrub_isbshrub(mdir, bshrub),
scratch_attrs, scratch_buf,
attrs, attr_count,
@@ -6845,20 +6729,97 @@ static int lfsr_bshrub_commit(lfs_t *lfs, lfsr_mdir_t *mdir,
// when btree is shrubbed, lfsr_btree_commit_ stops at the root
// and returns with pending attrs
//
// note! isbshrub may have changed in lfsr_btree_commit_ due to
// collapsed degenerate parents, splits, etc
// note! lfsr_bshrub_isbshrub may have changed state due to collapsed
// parents, splits, etc
//
if (attr_count > 0) {
int err = lfsr_mdir_commit(lfs, mdir, LFSR_ATTRS(
LFSR_ATTR(mdir->mid,
BSHRUBCOMMIT, 0, BSHRUBCOMMIT(
bshrub, attrs, attr_count))));
if (err) {
return err;
// we need to prevent our shrub from overflowing our mdir somehow
//
// maintaining an accurate estimate is tricky and error-prone,
// but recalculating an estimate every commit is expensive
//
// Instead, we keep track of an estimate of how many bytes have
// been progged to the shrub since the last estimate, and recalculate
// the estimate when this overflows our shrub_size. This mirrors how
// block_size and rbyds interact, and amortizes the estimate cost.
// figure out how much data this commit progs
lfs_size_t progged = 0;
for (lfs_size_t i = 0; i < attr_count; i++) {
// only include tag overhead if tag is not a grow tag
if (!lfsr_tag_isgrow(attrs[i].tag)) {
progged += LFSR_ATTR_ESTIMATE;
}
progged += lfsr_data_size(&attrs[i].data);
}
// does progged exceed our shrub_size? need to recalculate an
// accurate our estimate?
if (bshrub->progged + progged > lfs->cfg->shrub_size) {
lfs_ssize_t estimate = lfsr_rbyd_estimate(lfs,
&bshrub->rbyd, -1, -1, NULL);
if (estimate < 0) {
return estimate;
}
bshrub->progged = estimate;
}
// do we overflow shrub_size/2? the 1/2 here prevents runaway
// performance when the shrub is near full
if (bshrub->progged > lfs->cfg->shrub_size/2) {
// TODO am I missing a simpler function here? at least use
// lfsr_rbyd_commit once it doesn't maintain a copy...
// convert to btree
err = lfsr_rbyd_alloc(lfs, &bshrub->rbyd_);
if (err) {
return err;
}
err = lfsr_rbyd_appendcompactrbyd(lfs, &bshrub->rbyd_, false,
-1, -1, &bshrub->rbyd);
if (err) {
LFS_ASSERT(err != LFS_ERR_RANGE);
return err;
}
err = lfsr_rbyd_compact(lfs, &bshrub->rbyd_, false,
sizeof(uint32_t));
if (err) {
LFS_ASSERT(err != LFS_ERR_RANGE);
return err;
}
err = lfsr_rbyd_appendattrs(lfs, &bshrub->rbyd_, -1, -1,
attrs, attr_count);
if (err) {
LFS_ASSERT(err != LFS_ERR_RANGE);
return err;
}
err = lfsr_rbyd_appendcksum(lfs, &bshrub->rbyd_);
if (err) {
LFS_ASSERT(err != LFS_ERR_RANGE);
return err;
}
bshrub->rbyd = bshrub->rbyd_;
// otherwise commit to shrub like normal
} else {
int err = lfsr_mdir_commit(lfs, mdir, LFSR_ATTRS(
LFSR_ATTR(mdir->mid,
BSHRUBCOMMIT, 0, BSHRUBCOMMIT(
bshrub, attrs, attr_count))));
if (err) {
return err;
}
bshrub->progged += progged;
}
}
LFS_ASSERT(bshrub->btree.trunk != 0);
LFS_ASSERT(bshrub->rbyd.trunk != 0);
return 0;
}
@@ -6868,7 +6829,7 @@ static lfs_scmp_t lfsr_bshrub_namelookup(lfs_t *lfs, const lfsr_mdir_t *mdir,
lfsr_bid_t *bid_,
lfsr_tag_t *tag_, lfsr_bid_t *weight_, lfsr_data_t *data_) {
(void)mdir;
return lfsr_btree_namelookup(lfs, &bshrub->btree, did, name, name_size,
return lfsr_btree_namelookup(lfs, &bshrub->rbyd, did, name, name_size,
bid_, tag_, weight_, data_);
}
@@ -6879,10 +6840,10 @@ static int lfsr_bshrub_traversalread(lfs_t *lfs, const lfsr_mdir_t *mdir,
// prevent bshrub root from being traversed, since this is just our mdir
if (lfsr_bshrub_isbshrub(mdir, bshrub)
&& btraversal->branch.trunk == 0) {
btraversal->branch = bshrub->btree;
btraversal->branch = bshrub->rbyd;
}
return lfsr_btree_traversalread(lfs, &bshrub->btree, btraversal,
return lfsr_btree_traversalread(lfs, &bshrub->rbyd, btraversal,
binfo);
}
@@ -6948,13 +6909,6 @@ static inline bool lfsr_traversal_isvalidate(
return traversal->flags & LFSR_TRAVERSAL_VALIDATE;
}
// needed for lfsr_traversal_read
#if 0 // TODO
static inline bool lfsr_tree_isnull(const lfsr_tree_t *tree);
static inline bool lfsr_tree_hasbptr(const lfsr_tree_t *tree);
static inline bool lfsr_tree_hasbtree(const lfsr_tree_t *tree);
#endif
// info returned by mtree traveral
typedef struct lfsr_tinfo {
lfsr_tag_t tag;
@@ -7226,10 +7180,10 @@ static int lfsr_traversal_read(lfs_t *lfs, lfsr_traversal_t *traversal,
// found a bshrub (inlined btree)?
} else if (err != LFS_ERR_NOENT && tag == LFSR_TAG_TRUNK) {
traversal->bshrub.btree = traversal->mdir.u.rbyd;
traversal->bshrub.rbyd = traversal->mdir.u.rbyd;
err = lfsr_data_readtrunk(lfs, &data,
&traversal->bshrub.btree.trunk,
(lfsr_rid_t*)&traversal->bshrub.btree.weight);
&traversal->bshrub.rbyd.trunk,
(lfsr_rid_t*)&traversal->bshrub.rbyd.weight);
if (err) {
return err;
}
@@ -7242,7 +7196,7 @@ static int lfsr_traversal_read(lfs_t *lfs, lfsr_traversal_t *traversal,
// found a btree?
} else if (err != LFS_ERR_NOENT && tag == LFSR_TAG_BTREE) {
err = lfsr_data_readbtree(lfs, &data,
&traversal->bshrub.btree);
&traversal->bshrub.rbyd);
if (err) {
return err;
}
@@ -9093,7 +9047,6 @@ int lfsr_file_opencfg(lfs_t *lfs, lfsr_file_t *file,
// may be a sprout (simple inlined data)
if (err != LFS_ERR_NOENT && tag == LFSR_TAG_DATA) {
file->u.bsprout.data = data;
file->size = lfsr_data_size(&file->u.bsprout.data);
// or a direct block
} else if (err != LFS_ERR_NOENT && tag == LFSR_TAG_BLOCK) {
@@ -9102,21 +9055,24 @@ int lfsr_file_opencfg(lfs_t *lfs, lfsr_file_t *file,
return err;
}
file->size = file->u.bptr.size;
// or a bshrub (inlined btree)
} else if (err != LFS_ERR_NOENT && tag == LFSR_TAG_TRUNK) {
lfs_size_t trunk;
lfsr_rid_t weight;
err = lfsr_data_readtrunk(lfs, &data, &trunk, &weight);
file->u.bshrub.rbyd = file->m.mdir.u.rbyd;
err = lfsr_data_readtrunk(lfs, &data,
&file->u.bshrub.rbyd.trunk,
(lfsr_rid_t*)&file->u.bshrub.rbyd.weight);
if (err) {
return err;
}
file->u.bshrub.btree = file->m.mdir.u.rbyd;
file->u.bshrub.btree.trunk = trunk;
file->u.bshrub.btree.weight = weight;
file->size = weight;
// find an estimate on the current shrub size, we need this
// to prevent our shrub from overflowing the mdir
lfs_ssize_t estimate = lfsr_rbyd_estimate(lfs,
&file->u.bshrub.rbyd, -1, -1, NULL);
if (estimate < 0) {
return estimate;
}
file->u.bshrub.progged = estimate;
// or a btree
} else if (err != LFS_ERR_NOENT && tag == LFSR_TAG_BTREE) {
@@ -9124,9 +9080,9 @@ int lfsr_file_opencfg(lfs_t *lfs, lfsr_file_t *file,
if (err) {
return err;
}
file->size = file->u.btree.weight;
}
file->size = lfsr_file_uweight(file);
}
}
@@ -9401,35 +9357,42 @@ static int lfsr_file_carve(lfs_t *lfs, lfsr_file_t *file,
// always convert to bshrub/btree when this function is called
if (!lfsr_file_isbshruborbtree(file)) {
lfsr_bshrub_t bshrub = {.btree=file->m.mdir.u.rbyd};
bshrub.btree.trunk = 0;
bshrub.btree.weight = 0;
// note bshrub commits must always be in our opened mdir list
//
// TODO is this a reasonable design?
//
lfsr_tag_t tag = 0;
lfs_off_t weight;
lfsr_data_t data;
uint8_t bptr_buf[LFSR_BPTR_DSIZE];
// have data?
if (lfsr_file_isbsprout(file) && lfsr_file_uweight(file) > 0) {
int err = lfsr_bshrub_commit(lfs,
&file->m.mdir, &bshrub, LFSR_ATTRS(
LFSR_ATTR(0,
DATA, +lfsr_file_uweight(file),
DATA(file->u.bsprout.data))));
if (err) {
return err;
}
tag = LFSR_TAG_DATA;
weight = lfsr_data_size(&file->u.bsprout.data);
data = file->u.bsprout.data;
// have bptr?
} else if (lfsr_file_isbptr(file) && lfsr_file_uweight(file) > 0) {
uint8_t bptr_buf[LFSR_BPTR_DSIZE];
tag = LFSR_TAG_BLOCK;
weight = lfsr_bptr_size(&file->u.bptr);
data = lfsr_data_frombptr(&file->u.bptr, bptr_buf);
}
// TODO should we have a sort of lfsr_bshrub_alloc?
file->u.bshrub.rbyd = file->m.mdir.u.rbyd;
file->u.bshrub.rbyd.trunk = 0;
file->u.bshrub.rbyd.weight = 0;
file->u.bshrub.progged = 0;
if (tag) {
int err = lfsr_bshrub_commit(lfs,
&file->m.mdir, &bshrub, LFSR_ATTRS(
&file->m.mdir, &file->u.bshrub, LFSR_ATTRS(
LFSR_ATTR(0,
BLOCK, +lfsr_file_uweight(file),
FROMBPTR(&file->u.bptr, bptr_buf))));
TAG(tag), +weight, DATA(data))));
if (err) {
return err;
}
}
file->u.bshrub = bshrub;
}
// try to carve any existing data
@@ -9438,13 +9401,15 @@ static int lfsr_file_carve(lfs_t *lfs, lfsr_file_t *file,
lfsr_tag_t tag_;
lfsr_bid_t weight_;
lfsr_data_t data_;
int err = lfsr_bshrub_lookupnext(lfs, &file->m.mdir, &file->u.bshrub,
pos,
int err = lfsr_file_ulookupnext(lfs, file,
pos,
&bid_, &tag_, &weight_, &data_);
if (err) {
LFS_ASSERT(err != LFS_ERR_NOENT);
return err;
}
LFS_ASSERT(tag_ == LFSR_TAG_DATA
|| tag_ == LFSR_TAG_BLOCK);
// note an entry can be both a left and right sibling!
@@ -10261,9 +10226,11 @@ int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) {
: (lfsr_file_isbptr(file))
? LFSR_ATTR(file->m.mdir.mid,
WIDE(BLOCK), 0, FROMBPTR(&file->u.bptr, buf))
: (lfsr_file_isbshrub(file))
? LFSR_ATTR(file->m.mdir.mid,
WIDE(BSHRUBTRUNK), 0, BSHRUBTRUNK(&file->u.bshrub))
: LFSR_ATTR(file->m.mdir.mid,
WIDE(BSHRUBTRUNK), 0,
BSHRUBTRUNK(&file->u.bshrub))));
WIDE(BTREE), 0, FROMBTREE(&file->u.btree, buf))));
if (err) {
goto failed;
}
+6 -4
View File
@@ -522,12 +522,14 @@ typedef struct lfsr_bsprout {
// bshrubs must always be associated with an mdir
//
// btree.block == mdir.blocks[0] => bshrub
// btree.block != mdir.blocks[0] => btree
// rbyd.block == mdir.blocks[0] => bshrub
// rbyd.block != mdir.blocks[0] => btree
typedef struct lfsr_bshrub {
lfsr_btree_t btree;
lfsr_rbyd_t rbyd;
// copy for staging
lfsr_btree_t btree_;
lfsr_rbyd_t rbyd_;
// an upper-bound estimate on the on-disk shrub size
lfs_size_t progged;
} lfsr_bshrub_t;
typedef struct lfsr_file {
+24 -24
View File
@@ -1271,23 +1271,23 @@ def frepr(mdir, rid, tag):
elif tag == TAG_REG:
size = 0
structs = []
# sprout?
# inlined data?
done, rid_, tag_, w_, j, d, data, _ = mdir.lookup(rid, TAG_DATA)
if not done and rid_ == rid and tag_ == TAG_DATA:
size = max(size, len(data))
structs.append('data 0x%x.%x' % (mdir.block, j+d))
# shrub?
done, rid_, tag_, w_, j, d, data, _ = mdir.lookup(rid, TAG_TRUNK)
if not done and rid_ == rid and tag_ == TAG_TRUNK:
weight, trunk = fromshrub(data)
size = max(size, weight)
structs.append('trunk 0x%x.%x' % (mdir.block, trunk))
# direct block?
done, rid_, tag_, w_, j, d, data, _ = mdir.lookup(rid, TAG_BLOCK)
if not done and rid_ == rid and tag_ == TAG_BLOCK:
size_, block, off = frombptr(data)
size = max(size, size_)
structs.append('block 0x%x.%x' % (block, off))
# inlined bshrub?
done, rid_, tag_, w_, j, d, data, _ = mdir.lookup(rid, TAG_TRUNK)
if not done and rid_ == rid and tag_ == TAG_TRUNK:
weight, trunk = fromshrub(data)
size = max(size, weight)
structs.append('trunk 0x%x.%x' % (mdir.block, trunk))
# indirect btree?
done, rid_, tag_, w_, j, d, data, _ = mdir.lookup(rid, TAG_BTREE)
if not done and rid_ == rid and tag_ == TAG_BTREE:
@@ -1306,7 +1306,7 @@ def dbg_fstruct(f, block_size, mdir, rid, tag, j, d, data, *,
# first decode possible rbyds/btrees, for sprouts/direct blocks we pretend
# the entry itself is a single-element btree
# sprout?
# inlined data?
if tag == TAG_DATA:
btree = Rbyd(
mdir.block,
@@ -1316,11 +1316,6 @@ def dbg_fstruct(f, block_size, mdir, rid, tag, j, d, data, *,
j,
0)
w = len(data)
# shrub?
elif tag == TAG_TRUNK:
weight, trunk = fromshrub(data)
btree = Rbyd.fetch(f, block_size, mdir.block, trunk)
w = weight
# direct block?
elif tag == TAG_BLOCK:
btree = Rbyd(
@@ -1332,6 +1327,11 @@ def dbg_fstruct(f, block_size, mdir, rid, tag, j, d, data, *,
0)
size, block, off = frombptr(data)
w = size
# inlined bshrub?
elif tag == TAG_TRUNK:
weight, trunk = fromshrub(data)
btree = Rbyd.fetch(f, block_size, mdir.block, trunk)
w = weight
# indirect btree?
elif tag == TAG_BTREE:
weight, block, trunk, cksum = frombtree(data)
@@ -2144,7 +2144,7 @@ def main(disk, mroots=None, *,
# print file contents?
if tag == TAG_REG and args.get('structs'):
# sprout?
# inlined sprout?
done, rid_, tag_, w_, j, d, data, _ = mdir.lookup(
rid, TAG_DATA)
if not done and rid_ == rid and tag_ == TAG_DATA:
@@ -2154,16 +2154,6 @@ def main(disk, mroots=None, *,
color=color,
args=args)
# shrub?
done, rid_, tag_, w_, j, d, data, _ = mdir.lookup(
rid, TAG_TRUNK)
if not done and rid_ == rid and tag_ == TAG_TRUNK:
dbg_fstruct(f, block_size,
mdir, rid_, tag_, j, d, data,
m_width=2*w_width+1,
color=color,
args=args)
# direct block?
done, rid_, tag_, w_, j, d, data, _ = mdir.lookup(
rid, TAG_BLOCK)
@@ -2174,6 +2164,16 @@ def main(disk, mroots=None, *,
color=color,
args=args)
# inlined bshrub?
done, rid_, tag_, w_, j, d, data, _ = mdir.lookup(
rid, TAG_TRUNK)
if not done and rid_ == rid and tag_ == TAG_TRUNK:
dbg_fstruct(f, block_size,
mdir, rid_, tag_, j, d, data,
m_width=2*w_width+1,
color=color,
args=args)
# indirect btree?
done, rid_, tag_, w_, j, d, data, _ = mdir.lookup(
rid, TAG_BTREE)