Fixed crystal_thresh=0 bugs

There was a mismatch between the lfs3_cfg comment and the actual
crystal_thresh math where crystal_thresh=0 would break things:

- In lfs3_file_flush_, crystal_thresh=0 meant we would never resume
  crystallization, leading to terrible, _terrible_, linear write
  performance.

- In lfs3_file_sync and lfs3_set, it's unclear if small file commit
  optimizations were working properly. I went ahead and added a
  lfs3_max(lfs3->cfg->crystal_thresh, 1) just to be safe.

The other references to crystal_thresh all check for >= crystal_thresh
conditions, so shouldn't be broken (except for an unrelated bug in
lfs3_file_flushset_).

The reason for this is because crystal_thresh=1 is technically the lower
bound for this math. Allowing crystal_thresh=0 is just a convenience,
and honestly allowing it may have a been a bad idea. Maybe we should
require crystal_thresh=1 at minimum? I added a TODO.

All the new v3 config needs revisiting anyways, for defaults, etc.

---

Curiously, this actually saved code? My best guess is maybe some weird
code path in lfs3_file_flush_ was eliminated:

           code          stack          ctx
  before: 37036           2352          684
  after:  37028 (-0.0%)   2352 (+0.0%)  684 (+0.0%)
This commit is contained in:
Christopher Haster
2025-08-22 22:08:01 -05:00
parent 2c67fb1ea2
commit eab526ad9f
2 changed files with 13 additions and 6 deletions

10
lfs3.c
View File

@ -12727,7 +12727,7 @@ int lfs3_file_opencfg_(lfs3_t *lfs3, lfs3_file_t *file,
if (lfs3_o_iswrset(file->b.h.flags)
&& file->cache.size <= lfs3->cfg->inline_size
&& file->cache.size <= lfs3->cfg->fragment_size
&& file->cache.size < lfs3->cfg->crystal_thresh) {
&& file->cache.size < lfs3_max(lfs3->cfg->crystal_thresh, 1)) {
// we need to mark as unsync for sync to do anything
file->b.h.flags |= LFS3_o_UNSYNC;
@ -13753,7 +13753,7 @@ static int lfs3_file_flushset_(lfs3_t *lfs3, lfs3_file_t *file,
// enough data for a block?
#ifndef LFS3_2BONLY
if (size > lfs3->cfg->crystal_thresh) {
if (size >= lfs3->cfg->crystal_thresh) {
// align down for prog alignment
lfs3_ssize_t d = lfs3_aligndown(
lfs3_min(size, lfs3->cfg->block_size),
@ -13867,7 +13867,9 @@ static int lfs3_file_flush_(lfs3_t *lfs3, lfs3_file_t *file,
&& lfs3_bptr_iserased(&file->leaf.bptr)
&& pos >= block_end
&& pos < block_start + lfs3->cfg->block_size
&& pos - block_end < lfs3->cfg->crystal_thresh
// if we're more than a crystal away, graft and check crystal
// heuristic before resuming
&& pos - block_end < lfs3_max(lfs3->cfg->crystal_thresh, 1)
// need to bail if we can't meet prog alignment
&& (pos + size) - block_end >= lfs3->cfg->prog_size) {
// mark as uncrystallized to avoid allocating a new block
@ -14738,7 +14740,7 @@ int lfs3_file_sync(lfs3_t *lfs3, lfs3_file_t *file) {
if (file->cache.size == lfs3_file_size_(file)
&& file->cache.size <= lfs3->cfg->inline_size
&& file->cache.size <= lfs3->cfg->fragment_size
&& file->cache.size < lfs3->cfg->crystal_thresh) {
&& file->cache.size < lfs3_max(lfs3->cfg->crystal_thresh, 1)) {
// discard any overwritten leaves, this also clears the
// LFS3_o_UNCRYST and LFS3_o_UNGRAFT flags
lfs3_file_discardleaf(file);

9
lfs3.h
View File

@ -562,12 +562,17 @@ struct lfs3_cfg {
lfs3_size_t fragment_size;
#endif
// TODO crystal_thresh=0 really just means crystal_thresh=1, should we
// allow crystal_thresh=0? crystal_thresh=0 => block_size/16 or
// block_size/8 is probably a better default. need to benchmark.
// Threshold for compacting multiple fragments into a block. Smaller
// values will crystallize more eagerly, reducing disk usage, but
// increasing the cost of random-writes.
//
// 0 only writes blocks, minimizing disk usage, while -1 or any value >
// block_size only writes fragments, minimizing random-write cost.
// 0 or 1 only writes blocks, minimizing disk usage, while -1 or any
// value > block_size only writes fragments, minimizing random-write
// cost.
#ifndef LFS3_RDONLY
lfs3_size_t crystal_thresh;
#endif