Implemented deferred btree inlining via cutoff parameter

This finally provides a solution for deferred B-tree inlining without
needing to evaluate attrs.

Deferred inlining is the idea that instead of inlining B-trees as soon
as the number of entries drops to either 1 or 0, we wait until a
compaction occurs to inline a B-tree. This accomplishes a few things:

1. Limits any extra reads for conditions to compaction time.

2. Avoids wasting erased bytes if we drop to 1 or 0 entries only
   temporarily.

3. Avoids excessive erase costs if we oscillate between ~1 and ~2
   entries.

Unfortunately after moving away from evaluating attrs, deferred inlining
became deceptively tricky.

In the current, non-evaluating-attr implementation, our btree commits
always lag one commit behind. When we compact, we first compact
everything currently in the rbyd, and then append any pending attr.
Never needing to evaluate the attrs removes a big chunk of logic as long
as we can assert that the largest attr set fits after compaction.

But this lagging of commits presents a problem for deferred inlining, if
we detect an inlinable tree during compaction, we can't be sure it's
_actually_ inlinable until we evaluate our attr. Which we really don't
want to do.

The solution here is to move the problem up a level. Instead of trying
to determine when to inline purely from the provided attr, we require
higher-level functions to provide this info in the form of a "cutoff".
Where, if compaction results in fewer entries than this cutoff, the
higher-level function can instead inline.

This effectively allows the higher-level functions to intercept
unnecessary compactions that can be inlined.

So far this solution seems to work quite well, with the added plus of
consolidating the corner cases around inlined/inlining btrees in these
higher-level functions.

---

Note that this has the peculiar side-effect of allowing zero-weight,
non-inlined B-trees. Our previous internal B-tree struct using the sign
of an integer to determine inline-ness, this was changed to use just the
sign-bit for the condition as a sort of ones-complement width field.

I think this sort of encoding may actually bit a tiny bit more
efficient. I was poking around with thumb code and noticed there is no
actual "abs" instruction, with gcc outputing an "it" sequence. But there
is a cheap bit-clear "bic" instruction.
This commit is contained in:
Christopher Haster
2023-04-10 14:35:06 -05:00
parent 5a5598930e
commit f35061c7eb
2 changed files with 287 additions and 183 deletions
+9 -7
View File
@@ -360,21 +360,23 @@ typedef struct lfsr_rbyd {
//
// Pointers we store:
// - block addresses => 1 leb128 => 5 bytes (worst case)
#define LFSR_BTREE_INLINE_SIZE 5
#define LFSR_BTREE_INLINESIZE 5
typedef union lfsr_btree {
// note this lines up with weight in lfsr_rbyd_t
//
// weight=0 => null btree
// weight<0 => inlined btree
// weight>0 => normal btree
lfs_ssize_t weight;
// sign(weight)=1 => inlined btree
// sign(weight)=0 => normal btree
//
// note due to defered inlining, both normal and inlined
// btrees can have a weight of 0
lfs_size_t weight;
lfsr_rbyd_t root;
struct {
lfs_ssize_t weight;
lfsr_tag_t tag;
uint16_t size;
uint8_t buffer[LFSR_BTREE_INLINE_SIZE];
uint8_t buffer[LFSR_BTREE_INLINESIZE];
} inlined;
} lfsr_btree_t;
@@ -391,7 +393,7 @@ typedef union lfsr_btree {
// union {
// struct {
// uint8_t size;
// uint8_t buffer[LFSR_BTREE_INLINE_SIZE];
// uint8_t buffer[LFSR_BTREE_INLINESIZE];
// } inlined;
//
// // if we're not inlined, point to the trunk rbyd block of the btree