Reworked lfsr_ftree_flush to try to minimize btree lookups

Mainly by not looking up left neighbors after the first of many
block/fragment/crystal writes.

The right neighbors should already be avoiding redundant lookups since
redundant lookups imply a full fragment/block on the not-last write.

Additionally, once we fail our crystallization check, we can assume all
future fragment writes will fail, so we only need to do that lookup
once.

There's probably still more to optimize, but the way these heuristics
interact are tricky...
This commit is contained in:
Christopher Haster
2023-12-11 11:52:12 -06:00
parent fcddef6f1a
commit 26afd8b118
+222 -214
View File
@@ -9591,9 +9591,9 @@ static int lfsr_ftree_carve(lfs_t *lfs,
static int lfsr_ftree_flush(lfs_t *lfs, static int lfsr_ftree_flush(lfs_t *lfs,
lfsr_mdir_t *mdir, lfsr_ftree_t *ftree, lfsr_mdir_t *mdir, lfsr_ftree_t *ftree,
lfs_off_t buffer_pos, const uint8_t *buffer, lfs_size_t buffer_size) { lfs_off_t pos, const uint8_t *buffer, lfs_size_t size) {
// this may take a multiple iterations as we write fragments/blocks lfs_off_t pos_ = pos;
while (buffer_size > 0) {
// first we need to figure out our current crystal, we do this // first we need to figure out our current crystal, we do this
// heuristically. // heuristically.
// //
@@ -9602,13 +9602,13 @@ static int lfsr_ftree_flush(lfs_t *lfs,
// //
lfs_off_t crystal_start; lfs_off_t crystal_start;
// at beginning of file? // at beginning of file?
if (buffer_pos < lfs->cfg->crystal_size) { if (pos_ < lfs->cfg->crystal_size) {
crystal_start = 0; crystal_start = 0;
// beyond the end of the tree? // beyond the end of the tree?
} else if (buffer_pos - lfs->cfg->crystal_size } else if (pos_ - lfs->cfg->crystal_size
>= lfsr_ftree_size(ftree)) { >= lfsr_ftree_size(ftree)) {
crystal_start = buffer_pos; crystal_start = pos_;
// find left crystal neighbor // find left crystal neighbor
} else { } else {
@@ -9617,7 +9617,7 @@ static int lfsr_ftree_flush(lfs_t *lfs,
lfsr_bid_t weight_; lfsr_bid_t weight_;
lfsr_bptr_t bptr_; lfsr_bptr_t bptr_;
int err = lfsr_ftree_lookupnext(lfs, mdir, ftree, int err = lfsr_ftree_lookupnext(lfs, mdir, ftree,
buffer_pos - lfs->cfg->crystal_size, pos_ - lfs->cfg->crystal_size,
&bid_, &tag_, &weight_, &bptr_); &bid_, &tag_, &weight_, &bptr_);
if (err) { if (err) {
LFS_ASSERT(err != LFS_ERR_NOENT); LFS_ASSERT(err != LFS_ERR_NOENT);
@@ -9629,18 +9629,20 @@ static int lfsr_ftree_flush(lfs_t *lfs,
// our crystal // our crystal
if (tag_ == LFSR_TAG_DATA if (tag_ == LFSR_TAG_DATA
&& bid_-(weight_-1)+lfsr_data_size(&bptr_.data) && bid_-(weight_-1)+lfsr_data_size(&bptr_.data)
>= buffer_pos - lfs->cfg->crystal_size) { >= pos_ - lfs->cfg->crystal_size) {
crystal_start = bid_-(weight_-1); crystal_start = bid_-(weight_-1);
// otherwise our neighbor determines our crystal boundary // otherwise our neighbor determines our crystal boundary
} else { } else {
crystal_start = lfs_min32(bid_+1, buffer_pos); crystal_start = lfs_min32(bid_+1, pos_);
} }
} }
// iteratively write blocks
while (size > 0) {
// if we haven't already exceeded our crystallization threshold, // if we haven't already exceeded our crystallization threshold,
// find right crystal neighbor // find right crystal neighbor
lfs_off_t crystal_end = buffer_pos + buffer_size; lfs_off_t crystal_end = pos_ + size;
if (crystal_end - crystal_start <= lfs->cfg->crystal_size if (crystal_end - crystal_start <= lfs->cfg->crystal_size
&& crystal_start + lfs->cfg->crystal_size && crystal_start + lfs->cfg->crystal_size
< lfsr_ftree_size(ftree)) { < lfsr_ftree_size(ftree)) {
@@ -9661,25 +9663,222 @@ static int lfsr_ftree_flush(lfs_t *lfs,
if (tag_ == LFSR_TAG_DATA) { if (tag_ == LFSR_TAG_DATA) {
crystal_end = lfs_max32( crystal_end = lfs_max32(
bid_-(weight_-1)+lfsr_data_size(&bptr_.data), bid_-(weight_-1)+lfsr_data_size(&bptr_.data),
buffer_pos + buffer_size); pos_ + size);
// otherwise treat as crystal boundary // otherwise treat as crystal boundary
} else { } else {
crystal_end = lfs_max32( crystal_end = lfs_max32(
bid_-(weight_-1), bid_-(weight_-1),
buffer_pos + buffer_size); pos_ + size);
} }
} }
// below our crystallization threshold? just append a fragment // below our crystallization threshold? fallback to writing fragments
if (crystal_end - crystal_start <= lfs->cfg->crystal_size) { if (crystal_end - crystal_start <= lfs->cfg->crystal_size) {
// TODO if we failed a crystalization check, can we write fragments break;
// in a loop? so no redundent crystalization check? }
// exceeded our crystallization threshold? compact into a new block
// TODO check for becksums somewhere?
// before we can compact we need to figure out the best block
// alignment, we use the entry immediately to the left of our
// crystal for this
lfs_off_t block_start = crystal_start;
if (block_start > 0
&& lfsr_ftree_size(ftree) > 0
// don't bother to lookup left after first fragment
&& pos_ == pos) {
lfsr_bid_t bid_;
lfsr_tag_t tag_;
lfsr_bid_t weight_;
lfsr_bptr_t bptr_;
int err = lfsr_ftree_lookupnext(lfs, mdir, ftree,
lfs_min32(
block_start-1,
lfsr_ftree_size(ftree)-1),
&bid_, &tag_, &weight_, &bptr_);
if (err) {
LFS_ASSERT(err != LFS_ERR_NOENT);
return err;
}
// is our left neighbor in the same block?
if (block_start - (bid_-(weight_-1)) < lfs->cfg->block_size
&& lfsr_data_size(&bptr_.data) > 0) {
block_start = bid_-(weight_-1);
// no? is our left neighbor at least our left block neighbor?
// align to block alignment
} else if (block_start - (bid_-(weight_-1))
< 2*lfs->cfg->block_size
&& lfsr_data_size(&bptr_.data) > 0) {
block_start = bid_-(weight_-1) + lfs->cfg->block_size;
}
}
// allocate a new block
lfs_block_t block;
int err = lfs_alloc(lfs, &block);
if (err) {
return err;
}
// TODO should lfs_alloc handle erase?
err = lfsr_bd_erase(lfs, block);
if (err) {
return err;
}
// compact data into our new block
//
// eagerly merge any right neighbors we see unless that would
// put us over our block size
lfs_off_t pos__ = block_start;
uint32_t cksum = 0;
while (pos__ < lfs_min32(
lfs->cfg->block_size + block_start,
lfs_max32(
pos_ + size,
lfsr_ftree_size(ftree)))) {
// keep track of the next highest priority data offset
lfs_ssize_t d = lfs_min32(
lfs->cfg->block_size + block_start,
lfs_max32(
pos_ + size,
lfsr_ftree_size(ftree))) - pos__;
// any data in our write buffer?
if (pos__ < pos_ + size) {
if (pos__ >= pos_) {
lfs_ssize_t d_ = lfs_min32(
d,
size - (pos__ - pos_));
err = lfsr_bd_prog(lfs, block, pos__ - block_start,
&buffer[pos__ - pos_], d_,
&cksum);
if (err) {
return err;
}
pos__ += d_;
d -= d_;
}
// buffered data takes priority
d = lfs_min32(d, pos_ - pos__);
}
// any data on disk?
if (pos__ < lfsr_ftree_size(ftree)) {
lfsr_bid_t bid;
lfsr_tag_t tag;
lfsr_bid_t weight;
lfsr_bptr_t bptr;
err = lfsr_ftree_lookupnext(lfs, mdir, ftree, pos__,
&bid, &tag, &weight, &bptr);
if (err) {
LFS_ASSERT(err != LFS_ERR_NOENT);
return err;
}
// make sure to include all of our crystal, or else this
// loop may never terminate
if (bid-(weight-1) >= crystal_end
// is this data a pure hole? stop early to better
// leverage becksums in sparse files
&& (pos__ >= bid-(weight-1)
+ lfsr_data_size(&bptr.data)
// does this data exceed our block_size?
// stop early to try to avoid messing up
// block alignment
|| bid-(weight-1) + lfsr_data_size(&bptr.data)
- block_start
> lfs->cfg->block_size)) {
break;
}
if (pos__ < bid-(weight-1) + lfsr_data_size(&bptr.data)) {
// note one important side-effect here is a strict
// data hint
lfs_ssize_t d_ = lfs_min32(
d,
lfsr_data_size(&bptr.data)
- (pos__ - (bid-(weight-1))));
err = lfsr_bd_progdata(lfs, block, pos__ - block_start,
lfsr_data_slice(bptr.data,
pos__ - (bid-(weight-1)),
d_),
&cksum);
if (err) {
return err;
}
pos__ += d_;
d -= d_;
}
// found a hole? just make sure next leaf takes priority
d = lfs_min32(d, bid+1 - pos__);
}
// found a hole? write zeros
// TODO do something better than byte-level progs here
for (lfs_size_t i = 0; i < (lfs_size_t)d; i++) {
err = lfsr_bd_prog(lfs, block, pos__ - block_start + i,
&(uint8_t){0}, 1,
&cksum);
if (err) {
return err;
}
}
pos__ += d;
}
lfs_off_t block_end = pos__;
// TODO validate?
// finalize our write
err = lfsr_bd_flush(lfs);
if (err) {
return err;
}
// create our block pointer
lfsr_bptr_t bptr = {
.data.u.disk.block = block,
.data.u.disk.off = 0,
.data.u.disk.size = block_end - block_start,
.cksize = block_end - block_start,
.cksum = cksum,
};
// and write it into our tree
uint8_t bptr_buf[LFSR_BPTR_DSIZE];
err = lfsr_ftree_carve(lfs, mdir, ftree,
block_start, block_end - block_start, 0,
LFSR_TAG_BLOCK, lfsr_data_frombptr(&bptr, bptr_buf));
if (err) {
return err;
}
// note compacting fragments -> blocks may not actually make any
// progress on flushing the buffer on the first pass
lfs_ssize_t d = lfs_max32(pos_, block_end) - pos_;
pos_ += d;
buffer += lfs_min32(d, size);
size -= lfs_min32(d, size);
crystal_start = block_end;
}
// iteratively write fragments (inlined leaves)
while (size > 0) {
// truncate to our fragment size // truncate to our fragment size
lfs_off_t fragment_start = buffer_pos; lfs_off_t fragment_start = pos_;
lfs_off_t fragment_end = fragment_start lfs_off_t fragment_end = fragment_start
+ lfs_min32(buffer_size, lfs->cfg->fragment_size); + lfs_min32(size, lfs->cfg->fragment_size);
lfsr_data_t data = LFSR_DATA_BUF( lfsr_data_t data = LFSR_DATA_BUF(
buffer, buffer,
fragment_end - fragment_start); fragment_end - fragment_start);
@@ -9690,10 +9889,9 @@ static int lfsr_ftree_flush(lfs_t *lfs,
// do we have a left sibling? // do we have a left sibling?
if (fragment_start > 0 if (fragment_start > 0
&& lfsr_ftree_size(ftree) >= fragment_start) { && lfsr_ftree_size(ftree) >= fragment_start
// TODO can we do this here?
// don't bother to lookup left after first fragment // don't bother to lookup left after first fragment
//&& i == 0) { && pos_ == pos) {
lfsr_bid_t bid_; lfsr_bid_t bid_;
lfsr_tag_t tag_; lfsr_tag_t tag_;
lfsr_bid_t weight_; lfsr_bid_t weight_;
@@ -9774,200 +9972,10 @@ static int lfsr_ftree_flush(lfs_t *lfs,
} }
// to next fragment // to next fragment
lfs_ssize_t d = fragment_end - buffer_pos; lfs_ssize_t d = fragment_end - pos_;
buffer_pos += d; pos_ += d;
buffer += lfs_min32(d, buffer_size); buffer += lfs_min32(d, size);
buffer_size -= lfs_min32(d, buffer_size); size -= lfs_min32(d, size);
// exceeded our crystallization threshold? compact into a new block
} else {
// TODO check for becksums somewhere?
// before we can compact we need to figure out the best block
// alignment, we use the entry immediately to the left of our
// crystal for this
lfs_off_t block_start = crystal_start;
if (block_start > 0 && lfsr_ftree_size(ftree) > 0) {
lfsr_bid_t bid_;
lfsr_tag_t tag_;
lfsr_bid_t weight_;
lfsr_bptr_t bptr_;
int err = lfsr_ftree_lookupnext(lfs, mdir, ftree,
lfs_min32(
block_start-1,
lfsr_ftree_size(ftree)-1),
&bid_, &tag_, &weight_, &bptr_);
if (err) {
LFS_ASSERT(err != LFS_ERR_NOENT);
return err;
}
// is our left neighbor in the same block?
if (block_start - (bid_-(weight_-1)) < lfs->cfg->block_size
&& lfsr_data_size(&bptr_.data) > 0) {
block_start = bid_-(weight_-1);
// no? is our left neighbor at least our left block neighbor?
// align to block alignment
} else if (block_start - (bid_-(weight_-1))
< 2*lfs->cfg->block_size
&& lfsr_data_size(&bptr_.data) > 0) {
block_start = bid_-(weight_-1) + lfs->cfg->block_size;
}
}
// allocate a new block
lfs_block_t block;
int err = lfs_alloc(lfs, &block);
if (err) {
return err;
}
// TODO should lfs_alloc handle erase?
err = lfsr_bd_erase(lfs, block);
if (err) {
return err;
}
// compact data into our new block
//
// eagerly merge any right neighbors we see unless that would
// put us over our block size
lfs_off_t pos = block_start;
uint32_t cksum = 0;
while (pos < lfs_min32(
lfs->cfg->block_size + block_start,
lfs_max32(
buffer_pos + buffer_size,
lfsr_ftree_size(ftree)))) {
// keep track of the next highest priority data offset
lfs_ssize_t d = lfs_min32(
lfs->cfg->block_size + block_start,
lfs_max32(
buffer_pos + buffer_size,
lfsr_ftree_size(ftree))) - pos;
// any data in our write buffer?
if (pos < buffer_pos + buffer_size) {
if (pos >= buffer_pos) {
lfs_ssize_t d_ = lfs_min32(
d,
buffer_size - (pos - buffer_pos));
err = lfsr_bd_prog(lfs, block, pos - block_start,
&buffer[pos - buffer_pos], d_,
&cksum);
if (err) {
return err;
}
pos += d_;
d -= d_;
}
// buffered data takes priority
d = lfs_min32(d, buffer_pos - pos);
}
// any data on disk?
if (pos < lfsr_ftree_size(ftree)) {
lfsr_bid_t bid;
lfsr_tag_t tag;
lfsr_bid_t weight;
lfsr_bptr_t bptr;
err = lfsr_ftree_lookupnext(lfs, mdir, ftree, pos,
&bid, &tag, &weight, &bptr);
if (err) {
LFS_ASSERT(err != LFS_ERR_NOENT);
return err;
}
// make sure to include all of our crystal, or else this
// loop may never terminate
if (bid-(weight-1) >= crystal_end
// is this data a pure hole? stop early to better
// leverage becksums in sparse files
&& (pos >= bid-(weight-1)
+ lfsr_data_size(&bptr.data)
// does this data exceed our block_size?
// stop early to try to avoid messing up
// block alignment
|| bid-(weight-1) + lfsr_data_size(&bptr.data)
- block_start
> lfs->cfg->block_size)) {
break;
}
if (pos < bid-(weight-1) + lfsr_data_size(&bptr.data)) {
// note one important side-effect here is a strict
// data hint
lfs_ssize_t d_ = lfs_min32(
d,
lfsr_data_size(&bptr.data)
- (pos - (bid-(weight-1))));
err = lfsr_bd_progdata(lfs, block, pos - block_start,
lfsr_data_slice(bptr.data,
pos - (bid-(weight-1)),
d_),
&cksum);
if (err) {
return err;
}
pos += d_;
d -= d_;
}
// found a hole? just make sure next leaf takes priority
d = lfs_min32(d, bid+1 - pos);
}
// found a hole? write zeros
// TODO do something better than byte-level progs here
for (lfs_size_t i = 0; i < (lfs_size_t)d; i++) {
err = lfsr_bd_prog(lfs, block, pos - block_start + i,
&(uint8_t){0}, 1,
&cksum);
if (err) {
return err;
}
}
pos += d;
}
lfs_off_t block_end = pos;
// TODO validate?
// finalize our write
err = lfsr_bd_flush(lfs);
if (err) {
return err;
}
// create our block pointer
lfsr_bptr_t bptr = {
.data.u.disk.block = block,
.data.u.disk.off = 0,
.data.u.disk.size = block_end - block_start,
.cksize = block_end - block_start,
.cksum = cksum,
};
// and write it into our tree
uint8_t bptr_buf[LFSR_BPTR_DSIZE];
err = lfsr_ftree_carve(lfs, mdir, ftree,
block_start, block_end - block_start, 0,
LFSR_TAG_BLOCK, lfsr_data_frombptr(&bptr, bptr_buf));
if (err) {
return err;
}
// note compacting fragments -> blocks may not actually make any
// progress on flushing the buffer on the first pass
lfs_ssize_t d = lfs_max32(buffer_pos, block_end) - buffer_pos;
buffer_pos += d;
buffer += lfs_min32(d, buffer_size);
buffer_size -= lfs_min32(d, buffer_size);
}
} }
return 0; return 0;