Allow crystal_thresh to go below prog_size

After sleeping on it, allowing crystal_thresh < prog_size makes more
sense than I initially thought, if only to better support the case where
prog_size = block_size (SD/eMMC).

It's true fragments + crystal_thresh were intended to avoid needing to
write padding to raw data blocks, but this only makes sense up until
block padding is cheaper than rbyd overheads. At ~block_size/4, rbyds vs
padded data blocks have roughly the same cost, and at ~block_size/2
rbyds use ~2x the storage due to logging/splitting. At ~block_size/2 we
definitely want to crystallize even if this is still below prog_size.

And it turns out allowing crystal_thresh < prog_size fixes the 512B
block size issues on SD/eMMC we were running into earlier!

---

Implementing this required some tweaks to lfs3_file_crystallize_:

1. We intentionally do not align down partial crystallizations if we
   can't satisfy prog alignment, as we risk making no progress
   in this case.

2. If we can't satisfy prog alignment, don't mark the bptr as erased.
   Resuming crystallization to an unaligned block is an error.

Unaligned progs should already be implicitly padded by the lower bd
caching logic, so not aligning should be all we need to do to pad data
blocks.

Oh, and also relax the crystal_thresh >= prog_size constraints.

Adds a bit of code, but the improved block usage on SD/eMMC will
hopefully be valuable:

           code          stack          ctx
  before: 37112           2352          684
  after:  37156 (+0.1%)   2352 (+0.0%)  684 (+0.0%)
This commit is contained in:
Christopher Haster
2025-08-29 22:16:33 -05:00
parent 2f6f7705f1
commit 0698c49e1b
+38 -47
View File
@@ -12727,9 +12727,7 @@ int lfs3_file_opencfg_(lfs3_t *lfs3, lfs3_file_t *file,
if (lfs3_o_iswrset(file->b.h.flags) if (lfs3_o_iswrset(file->b.h.flags)
&& file->cache.size <= lfs3->cfg->inline_size && file->cache.size <= lfs3->cfg->inline_size
&& file->cache.size <= lfs3->cfg->fragment_size && file->cache.size <= lfs3->cfg->fragment_size
&& file->cache.size < lfs3_max( && file->cache.size < lfs3_max(lfs3->cfg->crystal_thresh, 1)) {
lfs3->cfg->crystal_thresh,
lfs3->cfg->prog_size)) {
// we need to mark as unsync for sync to do anything // we need to mark as unsync for sync to do anything
file->b.h.flags |= LFS3_o_UNSYNC; file->b.h.flags |= LFS3_o_UNSYNC;
@@ -13272,9 +13270,7 @@ static int lfs3_file_graft_(lfs3_t *lfs3, lfs3_file_t *file,
// carve bptr into fragment? // 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_bptr_size(&l)
< lfs3_max( < lfs3_max(lfs3->cfg->crystal_thresh, 1))) {
lfs3->cfg->crystal_thresh,
lfs3->cfg->prog_size))) {
rattrs[rattr_count++] = LFS3_RATTR_DATA( rattrs[rattr_count++] = LFS3_RATTR_DATA(
LFS3_TAG_GROW | LFS3_TAG_MASK8 | LFS3_TAG_DATA, LFS3_TAG_GROW | LFS3_TAG_MASK8 | LFS3_TAG_DATA,
-(bid+1 - pos), -(bid+1 - pos),
@@ -13324,9 +13320,7 @@ static int lfs3_file_graft_(lfs3_t *lfs3, lfs3_file_t *file,
// carve bptr into fragment? // 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_bptr_size(&r)
< lfs3_max( < lfs3_max(lfs3->cfg->crystal_thresh, 1))) {
lfs3->cfg->crystal_thresh,
lfs3->cfg->prog_size))) {
r_rattr_ = LFS3_RATTR_DATA( r_rattr_ = LFS3_RATTR_DATA(
LFS3_TAG_DATA, bid+1 - (pos+weight), LFS3_TAG_DATA, bid+1 - (pos+weight),
&r.d); &r.d);
@@ -13457,7 +13451,9 @@ static int lfs3_file_crystallize_(lfs3_t *lfs3, lfs3_file_t *file,
block_pos + lfs3_min( block_pos + lfs3_min(
lfs3_aligndown( lfs3_aligndown(
(lfs3_off_t)crystal_max, (lfs3_off_t)crystal_max,
lfs3->cfg->prog_size), lfs3_min(
lfs3->cfg->prog_size,
lfs3_max(lfs3->cfg->crystal_thresh, 1))),
lfs3->cfg->block_size), lfs3->cfg->block_size),
lfs3_max( lfs3_max(
pos + size, pos + size,
@@ -13669,9 +13665,16 @@ static int lfs3_file_crystallize_(lfs3_t *lfs3, lfs3_file_t *file,
// doing this retroactively to the pcache greatly simplifies the // doing this retroactively to the pcache greatly simplifies the
// above loop, though we may end up reading more than is // above loop, though we may end up reading more than is
// strictly necessary // strictly necessary
lfs3_ssize_t d = (pos_ - block_pos) % lfs3->cfg->prog_size; //
lfs3->pcache.size -= d; // note we _don't_ do this if prog alignment violates
pos_ -= d; // crystal_thresh, as this would prevent crystallization when
// crystal_thresh < prog_size, it's a weird case, but this is
// useful for small blocks
lfs3_size_t d = (pos_ - block_pos) % lfs3->cfg->prog_size;
if (d < lfs3_max(lfs3->cfg->crystal_thresh, 1)) {
lfs3->pcache.size -= d;
pos_ -= d;
}
// finalize our write // finalize our write
int err = lfs3_bd_flush(lfs3, int err = lfs3_bd_flush(lfs3,
@@ -13691,8 +13694,12 @@ static int lfs3_file_crystallize_(lfs3_t *lfs3, lfs3_file_t *file,
file->leaf.weight = pos_ - file->leaf.pos; file->leaf.weight = pos_ - file->leaf.pos;
lfs3_bptr_init(&file->leaf.bptr, lfs3_bptr_init(&file->leaf.bptr,
LFS3_DATA_DISK(block_, off_, pos_ - file->leaf.pos), LFS3_DATA_DISK(block_, off_, pos_ - file->leaf.pos),
// mark as erased // mark as erased, unless crystal_thresh prevented
LFS3_BPTR_ISERASED | (pos_ - block_pos), // prog alignment
(((pos_ - block_pos) % lfs3->cfg->prog_size == 0)
? LFS3_BPTR_ISERASED
: 0)
| (pos_ - block_pos),
lfs3->pcksum); lfs3->pcksum);
// mark as ungrafted // mark as ungrafted
@@ -13763,13 +13770,13 @@ static int lfs3_file_flushset_(lfs3_t *lfs3, lfs3_file_t *file,
// enough data for a block? // enough data for a block?
#ifndef LFS3_2BONLY #ifndef LFS3_2BONLY
if (size >= lfs3_max( if (size >= lfs3->cfg->crystal_thresh) {
lfs3->cfg->crystal_thresh,
lfs3->cfg->prog_size)) {
// align down for prog alignment // align down for prog alignment
lfs3_ssize_t d = lfs3_aligndown( lfs3_ssize_t d = lfs3_aligndown(
lfs3_min(size, lfs3->cfg->block_size), lfs3_min(size, lfs3->cfg->block_size),
lfs3->cfg->prog_size); lfs3_min(
lfs3->cfg->prog_size,
lfs3_max(lfs3->cfg->crystal_thresh, 1)));
relocate:; relocate:;
// allocate a new block // allocate a new block
@@ -13881,11 +13888,11 @@ static int lfs3_file_flush_(lfs3_t *lfs3, lfs3_file_t *file,
&& pos < block_start + lfs3->cfg->block_size && pos < block_start + lfs3->cfg->block_size
// if we're more than a crystal away, graft and check crystal // if we're more than a crystal away, graft and check crystal
// heuristic before resuming // heuristic before resuming
&& pos - block_end < lfs3_max( && pos - block_end < lfs3_max(lfs3->cfg->crystal_thresh, 1)
lfs3->cfg->crystal_thresh,
lfs3->cfg->prog_size)
// need to bail if we can't meet prog alignment // need to bail if we can't meet prog alignment
&& (pos + size) - block_end >= lfs3->cfg->prog_size) { && (pos + size) - block_end >= lfs3_min(
lfs3->cfg->prog_size,
lfs3->cfg->crystal_thresh)) {
// mark as uncrystallized to avoid allocating a new block // mark as uncrystallized to avoid allocating a new block
file->b.h.flags |= LFS3_o_UNCRYST; file->b.h.flags |= LFS3_o_UNCRYST;
// crystallize // crystallize
@@ -13934,13 +13941,9 @@ static int lfs3_file_flush_(lfs3_t *lfs3, lfs3_file_t *file,
// if we haven't already exceeded our crystallization threshold, // if we haven't already exceeded our crystallization threshold,
// find left crystal neighbor // find left crystal neighbor
lfs3_off_t poke = lfs3_smax( lfs3_off_t poke = lfs3_smax(
crystal_start - (lfs3_max( crystal_start - (lfs3->cfg->crystal_thresh-1),
lfs3->cfg->crystal_thresh,
lfs3->cfg->prog_size)-1),
0); 0);
if (crystal_end - crystal_start < lfs3_max( if (crystal_end - crystal_start < lfs3->cfg->crystal_thresh
lfs3->cfg->crystal_thresh,
lfs3->cfg->prog_size)
&& crystal_start > 0 && crystal_start > 0
&& poke < file->b.shrub.r.weight && poke < file->b.shrub.r.weight
// don't bother looking up left after the first block // don't bother looking up left after the first block
@@ -13974,13 +13977,9 @@ static int lfs3_file_flush_(lfs3_t *lfs3, lfs3_file_t *file,
// 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
poke = lfs3_min( poke = lfs3_min(
crystal_start + (lfs3_max( crystal_start + (lfs3->cfg->crystal_thresh-1),
lfs3->cfg->crystal_thresh,
lfs3->cfg->prog_size)-1),
file->b.shrub.r.weight-1); file->b.shrub.r.weight-1);
if (crystal_end - crystal_start < lfs3_max( if (crystal_end - crystal_start < lfs3->cfg->crystal_thresh
lfs3->cfg->crystal_thresh,
lfs3->cfg->prog_size)
&& crystal_end < file->b.shrub.r.weight) { && crystal_end < file->b.shrub.r.weight) {
lfs3_bid_t bid; lfs3_bid_t bid;
lfs3_bid_t weight; lfs3_bid_t weight;
@@ -14015,9 +14014,7 @@ static int lfs3_file_flush_(lfs3_t *lfs3, lfs3_file_t *file,
// //
// note as long as crystal_thresh >= prog_size, this also ensures we // note as long as crystal_thresh >= prog_size, this also ensures we
// have enough for prog alignment // have enough for prog alignment
if (crystal_end - crystal_start < lfs3_max( if (crystal_end - crystal_start < lfs3->cfg->crystal_thresh) {
lfs3->cfg->crystal_thresh,
lfs3->cfg->prog_size)) {
goto fragment; goto fragment;
} }
@@ -14765,9 +14762,7 @@ int lfs3_file_sync(lfs3_t *lfs3, lfs3_file_t *file) {
if (file->cache.size == lfs3_file_size_(file) if (file->cache.size == lfs3_file_size_(file)
&& file->cache.size <= lfs3->cfg->inline_size && file->cache.size <= lfs3->cfg->inline_size
&& file->cache.size <= lfs3->cfg->fragment_size && file->cache.size <= lfs3->cfg->fragment_size
&& file->cache.size < lfs3_max( && file->cache.size < lfs3_max(lfs3->cfg->crystal_thresh, 1)) {
lfs3->cfg->crystal_thresh,
lfs3->cfg->prog_size)) {
// discard any overwritten leaves, this also clears the // discard any overwritten leaves, this also clears the
// LFS3_o_UNCRYST and LFS3_o_UNGRAFT flags // LFS3_o_UNCRYST and LFS3_o_UNGRAFT flags
lfs3_file_discardleaf(file); lfs3_file_discardleaf(file);
@@ -14993,9 +14988,7 @@ int lfs3_file_truncate(lfs3_t *lfs3, lfs3_file_t *file, lfs3_off_t size_) {
if (!lfs3_bptr_isbptr(&file->leaf.bptr) 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_bptr_size(&file->leaf.bptr)
< lfs3_max( < lfs3_max(lfs3->cfg->crystal_thresh, 1))) {
lfs3->cfg->crystal_thresh,
lfs3->cfg->prog_size))) {
lfs3_file_discardleaf(file); lfs3_file_discardleaf(file);
} }
@@ -15088,9 +15081,7 @@ int lfs3_file_fruncate(lfs3_t *lfs3, lfs3_file_t *file, lfs3_off_t size_) {
if (!lfs3_bptr_isbptr(&file->leaf.bptr) 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_bptr_size(&file->leaf.bptr)
< lfs3_max( < lfs3_max(lfs3->cfg->crystal_thresh, 1))) {
lfs3->cfg->crystal_thresh,
lfs3->cfg->prog_size))) {
lfs3_file_discardleaf(file); lfs3_file_discardleaf(file);
} }