btree: Resurrected btree leaf caching

This is an indulgence to simplify the upcoming auxiliary btree work.

Brings back the previously-reverted per-btree leaf caches, where each
lfs3_btree_t keeps track of two rbyds: The root and the most recently
accessed leaf.

At the surface level, this optimizes repeated access to the same btree
leaf. A common pattern for a number of littlefs's operations that has
proven tricky to manually optimize:

- Btree iteration
- Pokes for our crystalization heuristic
- Checksum collision resolution for dids and (FUTURE) ddkeys
- Related rattrs attached to a single bid

But the real motivation is to drop lfs3_btree_*lookupleaf and simplify
the internal APIs. If repeated lfs3_btree_lookup*s are already
efficient, there's no reason for extra leaf-level APIs, and in theory
any logic that interacts with btrees will be simpler.

---

This comes at a cost (humorously about the same amount as the
tag-returning refactor, if you ignore the extra 28 bytes of ctx).
Unsurprisingly, increasing the size of lfs3_btree_t has the biggest
impact on stack and ctx:

           code          stack          ctx
  before: 36084           2336          656
  after:  36784 (+1.9%)   2400 (+2.7%)  684 (+4.3%)

Also note from the previous commit messages: Btree leaf caching has
resulted in surprisingly little performance improvement for our current
benchmarks + implementation. It turns out if you're dominated by write
cost, optimizing btree lookups -- which already skip rbyd fetches, has
barely noticeable impact.

---

A note on reverting!

Eventually (after the auxiliary btree work) it will probably make sense
to revert this -- or at least provide a non-leaf-caching build for
code/RAM sensitive users.

I don't think this should be reverted as-is. Instead, I think we should
allow the option to just disable the leaf cache, while keeping the
simpler internal API. This would give us the best of all three worlds:

- A small code/RAM option
- Optimal btree iteration/nearby-lookup performance
- Simpler internal APIs

The only reason this isn't already implemented is because I want to
avoid fragmenting the codebase further while we're still in development
mode.
This commit is contained in:
Christopher Haster
2025-07-20 12:33:19 -05:00
parent ba9a45aa01
commit cd9f93d859
7 changed files with 687 additions and 697 deletions
+14 -3
View File
@@ -681,8 +681,19 @@ typedef struct lfs3_rbyd {
uint32_t cksum;
} lfs3_rbyd_t;
// a btree is represented by the root rbyd
typedef lfs3_rbyd_t lfs3_btree_t;
// littlefs's btree representation
//
// technically all we need for btrees is the root rbyd, but tracking the
// most recent leaf helps speed up iteration/subattrs/etc without
// local rbyd allocations -- less code and stack for the same
// performance
typedef struct lfs3_btree {
lfs3_rbyd_t r;
struct {
lfs3_bid_t bid;
lfs3_rbyd_t rbyd;
} leaf;
} lfs3_btree_t;
// littlefs's atomic metadata log type
typedef struct lfs3_mdir {
@@ -712,7 +723,7 @@ typedef struct lfs3_bshrub {
// trunk=0 => no bshrub/btree
// sign(trunk)=1 => bshrub
// sign(trunk)=0 => btree
lfs3_shrub_t shrub;
lfs3_btree_t shrub;
#ifndef LFS3_RDONLY
lfs3_shrub_t shrub_;
#endif