Prevent oscillation when crystal_thresh < fragment_size

When crystal_thresh < fragment_size, there was a risk that repeated
write operations would oscillate between crystallizing and fragmenting
every operation. Not only would this wreck performance, it would also
violently wear down blocks as each crystallization would trigger an
erase.

Fortunately all we need to do to prevent this is check both
fragment_size and crystal_thresh before fragmenting. Note this also
affects the fragment checks in truncate/fruncate.

---

crystal_thresh < fragment_size is kind of a weird configuration, to be
honest we should probably just assert if configured this way (we never
write fragments > crystal_thresh, because at that point we would just
crystallize).

But at the moment the extra leniency is useful for benchmarking.

Adds a bit of code, but will probably either assert or mutably limit in
the future:

           code          stack          ctx
  before: 37028           2352          684
  after:  37076 (+0.1%)   2352 (+0.0%)  684 (+0.0%)
This commit is contained in:
Christopher Haster
2025-08-24 23:02:19 -05:00
parent eab526ad9f
commit 8cc91ffa9e
2 changed files with 15 additions and 6 deletions

18
lfs3.c
View File

@ -13268,7 +13268,9 @@ static int lfs3_file_graft_(lfs3_t *lfs3, lfs3_file_t *file,
// carve fragment?
} else if (!lfs3_bptr_isbptr(&bptr_)
// carve bptr into fragment?
|| lfs3_bptr_size(&l) <= lfs3->cfg->fragment_size) {
|| (lfs3_bptr_size(&l) <= lfs3->cfg->fragment_size
&& lfs3_bptr_size(&l)
< lfs3_max(lfs3->cfg->crystal_thresh, 1))) {
rattrs[rattr_count++] = LFS3_RATTR_DATA(
LFS3_TAG_GROW | LFS3_TAG_MASK8 | LFS3_TAG_DATA,
-(bid+1 - pos),
@ -13316,7 +13318,9 @@ static int lfs3_file_graft_(lfs3_t *lfs3, lfs3_file_t *file,
// carve fragment?
} else if (!lfs3_bptr_isbptr(&bptr_)
// carve bptr into fragment?
|| lfs3_bptr_size(&r) <= lfs3->cfg->fragment_size) {
|| (lfs3_bptr_size(&r) <= lfs3->cfg->fragment_size
&& lfs3_bptr_size(&r)
< lfs3_max(lfs3->cfg->crystal_thresh, 1))) {
r_rattr_ = LFS3_RATTR_DATA(
LFS3_TAG_DATA, bid+1 - (pos+weight),
&r.d);
@ -14964,8 +14968,9 @@ int lfs3_file_truncate(lfs3_t *lfs3, lfs3_file_t *file, lfs3_off_t size_) {
// discard if our leaf is a fragment, is fragmented, or is completed
// truncated, we can't rely on any in-bshrub/btree state
if (!lfs3_bptr_isbptr(&file->leaf.bptr)
|| lfs3_bptr_size(&file->leaf.bptr)
<= lfs3->cfg->fragment_size) {
|| (lfs3_bptr_size(&file->leaf.bptr) <= lfs3->cfg->fragment_size
&& lfs3_bptr_size(&file->leaf.bptr)
< lfs3_max(lfs3->cfg->crystal_thresh, 1))) {
lfs3_file_discardleaf(file);
}
@ -15056,8 +15061,9 @@ int lfs3_file_fruncate(lfs3_t *lfs3, lfs3_file_t *file, lfs3_off_t size_) {
// discard if our leaf is a fragment, is fragmented, or is completed
// truncated, we can't rely on any in-bshrub/btree state
if (!lfs3_bptr_isbptr(&file->leaf.bptr)
|| lfs3_bptr_size(&file->leaf.bptr)
<= lfs3->cfg->fragment_size) {
|| (lfs3_bptr_size(&file->leaf.bptr) <= lfs3->cfg->fragment_size
&& lfs3_bptr_size(&file->leaf.bptr)
< lfs3_max(lfs3->cfg->crystal_thresh, 1))) {
lfs3_file_discardleaf(file);
}

3
lfs3.h
View File

@ -566,6 +566,9 @@ struct lfs3_cfg {
// allow crystal_thresh=0? crystal_thresh=0 => block_size/16 or
// block_size/8 is probably a better default. need to benchmark.
// TODO we should probably just assert if crystal_thresh < fragment_size,
// or if crystal_thresh < prog_size, these aren't really valid cases
// Threshold for compacting multiple fragments into a block. Smaller
// values will crystallize more eagerly, reducing disk usage, but
// increasing the cost of random-writes.