Relax recrystallization when fruncating/logging

This was a nasty performance hole found while benchmarking.

Basically, any time crystallization is triggered, the crystallization
algorithm tries to pack as much data into as few blocks as possible.
When fruncating (the common, and performance sensitive, use case being
logging), this can lead to the algorithm rewriting fruncated blocks.

What the crystallization algorithm doesn't realize, however, is that
when fruncating/logging, we're probably going to fruncate again on the
next call, so rewriting the block is a waste of effort.

Worst case -- a 1 block file -- this can cause littlefs to rewrite the
entire file on every append.

---

The solution implemented here, which is a bit of a hack, is to use the
actual block start for block alignment instead of the logical
start-of-block referenced by our btree/bshrub.

This solves the fruncating/logging performance hole, with the tradeoff
of using more storage than is strictly necessary. This tradeoff is
probably expected with logging however.

Code changes minimal:

           code          stack          ctx
  before: 37024           2352          684
  after:  37036 (+0.0%)   2352 (+0.0%)  684 (+0.0%)
This commit is contained in:
Christopher Haster
2025-08-21 11:32:21 -05:00
parent 14d0c4121c
commit c33182b49b

11
lfs3.c
View File

@ -14062,17 +14062,22 @@ static int lfs3_file_flush_(lfs3_t *lfs3, lfs3_file_t *file,
}
// is our left neighbor in the same block?
if (crystal_start - (bid-(weight-1))
//
// note we use the actual block start here! not the sliced
// view! this avoids excessive recrystallizations when
// fruncating
if (crystal_start - (bid-(weight-1)-lfs3_bptr_off(&bptr))
< lfs3->cfg->block_size
&& lfs3_bptr_size(&bptr) > 0) {
crystal_start = bid-(weight-1);
// no? is our left neighbor at least our left block neighbor?
// align to block alignment
} else if (crystal_start - (bid-(weight-1))
} else if (crystal_start - (bid-(weight-1)-lfs3_bptr_off(&bptr))
< 2*lfs3->cfg->block_size
&& lfs3_bptr_size(&bptr) > 0) {
crystal_start = bid-(weight-1) + lfs3->cfg->block_size;
crystal_start = bid-(weight-1)-lfs3_bptr_off(&bptr)
+ lfs3->cfg->block_size;
}
}