Reworked flushbuffer logic to merge neighboring pieces of data

This gets pretty ugly and mainly just involves a lot of subtle range
logic.

Our CAT data representation really shines here, but all of the scratch
datas do come with a code/ram cost:

            code          stack
  before:  25448           1920
  after:   25672 (+0.9%)   2024 (+5.1%)
This commit is contained in:
Christopher Haster
2023-09-23 23:58:36 -05:00
parent 4334a848a3
commit 2b950bb16b
+179 -123
View File
@@ -984,7 +984,10 @@ static lfs_ssize_t lfsr_bd_progtag(lfs_t *lfs,
// through the LFSR_ATTR macro // through the LFSR_ATTR macro
#define LFSR_DATA_DATA(_data) (_data) #define LFSR_DATA_DATA(_data) (_data)
#define LFSR_DATA_NULL LFSR_DATA_BUF(NULL, 0) #define LFSR_DATA_NULL \
((lfsr_data_t){ \
.u.inlined.size=0, \
.u.inlined.count=0})
#define LFSR_DATA_IMM(_buffer, _size) \ #define LFSR_DATA_IMM(_buffer, _size) \
lfsr_data_fromimm(_buffer, _size) lfsr_data_fromimm(_buffer, _size)
@@ -8367,117 +8370,98 @@ lfs_ssize_t lfsr_file_read(lfs_t *lfs, lfsr_file_t *file,
static int lfsr_file_flushbuffer(lfs_t *lfs, lfsr_file_t *file) { static int lfsr_file_flushbuffer(lfs_t *lfs, lfsr_file_t *file) {
while (file->buffer_size > 0) { while (file->buffer_size > 0) {
// do we need an inlined tree? // figure out how to flush
if (!lfsr_file_hasshrub(file)) { lfsr_attr_t hole_attr = LFSR_ATTR_NOOP;
// TODO rm? we haven't updated file->size yet! lfs_off_t pos = file->buffer_pos;
// // we shouldn't reach this point if we still fit entirely lfs_off_t weight = file->buffer_size;
// // in a simple inlined file lfsr_data_t datas[3] = {
// LFS_ASSERT(file->size > lfs_min32( LFSR_DATA_NULL,
// lfs->cfg->cache_size, LFSR_DATA_BUF(file->buffer, file->buffer_size),
// lfs->cfg->inline_size)); LFSR_DATA_NULL,
};
// do we carve out any data from the inlined data? // have inlined data?
lfsr_data_t left_data = LFSR_DATA_DISK( if (!lfsr_file_hasshrub(file)) {
// merge/carve inlined data and our buffer?
if (lfsr_data_size(&file->inlined.u.data) >= file->buffer_pos) {
pos = 0;
weight = lfs_max32(
file->buffer_pos + file->buffer_size,
lfsr_data_size(&file->inlined.u.data));
datas[0] = LFSR_DATA_DISK(
file->inlined.u.data.u.disk.block, file->inlined.u.data.u.disk.block,
file->inlined.u.data.u.disk.off, file->inlined.u.data.u.disk.off,
lfs_min32( lfs_min32(
file->buffer_pos, lfsr_data_size(&file->inlined.u.data),
lfsr_file_inlinedsize(file))); file->buffer_pos));
datas[2] = LFSR_DATA_DISK(
lfs_off_t right_pos = file->buffer_pos + file->buffer_size;
lfsr_data_t right_data = LFSR_DATA_DISK(
file->inlined.u.data.u.disk.block, file->inlined.u.data.u.disk.block,
file->inlined.u.data.u.disk.off + right_pos, file->inlined.u.data.u.disk.off
lfsr_file_inlinedsize(file) - lfs_min32( + file->buffer_pos + file->buffer_size,
right_pos, lfsr_data_size(&file->inlined.u.data) - lfs_min32(
lfsr_file_inlinedsize(file))); file->buffer_pos + file->buffer_size,
lfsr_data_size(&file->inlined.u.data)));
// TODO this is handled for us in lfsr_mdir_commit__ now to avoid compaction // we can't merge if we have a hole, create two-leaf shrub
// issues } else {
// // create a zero weight trunk hole_attr = LFSR_ATTR(0,
// // SHRUB(INLINED), +file->buffer_pos, DATA(
// // make sure to use our staging rbyd so we catch in-flight updates file->inlined.u.data));
// // caused by mdir compactions
// file->inlined_.u.rbyd.block = file->m.mdir.u.m.blocks[0];
// file->inlined_.u.rbyd.trunk = 0;
// file->inlined_.u.rbyd.weight = 0;
// // TODO
// file->inlined_.u.deferred.overhead = 0;
// convert to inlined tree
//
// note that the actual conversion from inlined data to tree is
// in lfsr_mdir_commit__ to avoid compaction issues
int err = lfsr_mdir_commit(lfs, &file->m.mdir, LFSR_ATTRS(
LFSR_ATTR_(file->m.mdir.mid, SHRUBATTRS, 0, SHRUBATTRS(file,
// note we always need a zero entry
(file->buffer_pos > 0
? LFSR_ATTR(0,
SHRUB(INLINED),
+file->buffer_pos,
DATA(left_data))
: LFSR_ATTR_NOOP),
LFSR_ATTR(file->buffer_pos,
SHRUB(INLINED),
+file->buffer_size,
BUF(file->buffer, file->buffer_size)),
(lfsr_data_size(&right_data) > 0
? LFSR_ATTR(right_pos,
SHRUB(INLINED),
+lfsr_data_size(&right_data),
DATA(right_data))
: LFSR_ATTR_NOOP)))));
if (err) {
return err;
} }
// TODO // have a shrub?
file->inlined.u.shrub.overhead = 0; } else {
file->buffer_size = 0; // this should never happen, every route to zero-weight shrub
continue; // should revert to an inlined file
} LFS_ASSERT(file->inlined.u.rbyd.weight > 0);
// TODO can we merge this with the above once we find the data_ts? // find left sibling
// do we fit in our inlined tree?
//
// this is a complex question since we may be carving out leaves of the
// inlined tree, we need to find these leaves to know for sure
//
lfsr_srid_t left_rid; lfsr_srid_t left_rid;
lfsr_tag_t left_tag; lfsr_tag_t left_tag;
lfsr_rid_t left_weight; lfsr_rid_t left_weight;
lfsr_data_t left_data; lfsr_data_t left_data;
int err = lfsr_rbyd_lookupnext(lfs, &file->inlined.u.rbyd, int err = lfsr_rbyd_lookupnext(lfs, &file->inlined.u.rbyd,
lfs_min32(file->buffer_pos, file->inlined.u.rbyd.weight-1), 0, lfs_min32(
file->buffer_pos,
file->inlined.u.rbyd.weight)-1, 0,
&left_rid, &left_tag, &left_weight, &left_data); &left_rid, &left_tag, &left_weight, &left_data);
if (err && err != LFS_ERR_NOENT) { if (err && err != LFS_ERR_NOENT) {
return err; return err;
} }
LFS_ASSERT(err == LFS_ERR_NOENT LFS_ASSERT(err != LFS_ERR_NOENT);
|| left_tag == LFSR_TAG_SHRUB(INLINED)); LFS_ASSERT(left_tag == LFSR_TAG_SHRUB(INLINED));
// figure out what data we carve out, we need to update these // left sibling overlaps? need to merge/carve
lfs_off_t left_pos; // left sibling touches? only merge if we won't end up
if (err != LFS_ERR_NOENT // carving later
&& (lfs_off_t)left_rid+1 > file->buffer_pos) { lfs_soff_t left_overlap
left_pos = left_rid-(left_weight-1); = (left_rid-(left_weight-1) + lfsr_data_size(&left_data))
left_data = LFSR_DATA_DISK( - file->buffer_pos;
if (left_overlap > 0
|| (left_overlap == 0
// TODO use a different heuristic than cache_size here?
&& weight + lfsr_data_size(&left_data)
<= lfs->cfg->cache_size)) {
pos = left_rid - (left_weight-1);
weight += file->buffer_pos - pos;
datas[0] = LFSR_DATA_DISK(
left_data.u.disk.block, left_data.u.disk.block,
left_data.u.disk.off, left_data.u.disk.off,
lfsr_data_size(&left_data) file->buffer_pos - pos);
- ((left_rid+1)-file->buffer_pos));
} else { } else {
left_pos = file->buffer_pos; // need a hole?
left_data = LFSR_DATA_NULL; if (left_overlap < 0) {
hole_attr = LFSR_ATTR(left_rid, GROW, -left_overlap, NULL);
}
} }
// find right sibling
lfsr_srid_t right_rid; lfsr_srid_t right_rid;
lfsr_tag_t right_tag; lfsr_tag_t right_tag;
lfsr_rid_t right_weight; lfsr_rid_t right_weight;
lfsr_data_t right_data; lfsr_data_t right_data;
err = lfsr_rbyd_lookupnext(lfs, &file->inlined.u.rbyd, err = lfsr_rbyd_lookupnext(lfs, &file->inlined.u.rbyd,
file->buffer_pos + file->buffer_size-1, 0, file->buffer_pos + file->buffer_size, 0,
&right_rid, &right_tag, &right_weight, &right_data); &right_rid, &right_tag, &right_weight, &right_data);
if (err && err != LFS_ERR_NOENT) { if (err && err != LFS_ERR_NOENT) {
return err; return err;
@@ -8485,54 +8469,126 @@ static int lfsr_file_flushbuffer(lfs_t *lfs, lfsr_file_t *file) {
LFS_ASSERT(err == LFS_ERR_NOENT LFS_ASSERT(err == LFS_ERR_NOENT
|| right_tag == LFSR_TAG_SHRUB(INLINED)); || right_tag == LFSR_TAG_SHRUB(INLINED));
lfs_off_t right_pos; if (err != LFS_ERR_NOENT) {
if (err != LFS_ERR_NOENT // right sibling overlaps? need to merge/carve
&& right_rid-(right_weight-1) // right sibling touches? only merge if we won't end up
< file->buffer_pos + file->buffer_size) { // carving later
right_pos = file->buffer_pos + file->buffer_size; lfs_soff_t right_overlap
right_data = LFSR_DATA_DISK( = (file->buffer_pos + file->buffer_size)
- (right_rid - (right_weight-1));
LFS_ASSERT(right_overlap >= 0);
if (right_overlap > 0
|| (right_overlap == 0
// TODO use a different heuristic than cache_size
// here?
&& weight + lfsr_data_size(&right_data)
<= lfs->cfg->cache_size)) {
// note physical right data size risks going negative here
// because of holes
weight += right_weight - right_overlap;
datas[2] = LFSR_DATA_DISK(
right_data.u.disk.block, right_data.u.disk.block,
right_data.u.disk.off right_data.u.disk.off + right_overlap,
+ (file->buffer_pos + file->buffer_size lfsr_data_size(&right_data) - lfs_min32(
- right_rid-(right_weight-1)), right_overlap,
lfsr_data_size(&right_data) lfsr_data_size(&right_data)));
- (file->buffer_pos + file->buffer_size }
- right_rid-(right_weight-1))); }
} else {
right_pos = file->buffer_pos + file->buffer_size;
right_data = LFSR_DATA_NULL;
} }
// write out our buffer and any carved data // can we coalesce any of our data?
err = lfsr_mdir_commit(lfs, &file->m.mdir, LFSR_ATTRS( // TODO a better way to do this?
lfsr_attr_t coalesce_attrs[3] = {
LFSR_ATTR_NOOP,
LFSR_ATTR_NOOP,
LFSR_ATTR_NOOP,
};
if (lfsr_data_size(&datas[0])
+ lfsr_data_size(&datas[1])
+ lfsr_data_size(&datas[2])
// TODO use a different heuristic than cache_size here?
//
// make sure to never write null siblings, even when
// buffer > cache size
<= lfs_max32(lfs->cfg->cache_size, file->buffer_size)) {
coalesce_attrs[0] = LFSR_ATTR(pos,
SHRUB(INLINED),
+weight,
DATA(lfsr_data_fromcat(datas, 3)));
} else if (lfsr_data_size(&datas[0])
+ lfsr_data_size(&datas[1])
// TODO use a different heuristic than cache_size here?
//
// make sure to never write null siblings, even when
// buffer > cache size
<= lfs_max32(lfs->cfg->cache_size, file->buffer_size)) {
coalesce_attrs[0] = LFSR_ATTR(pos,
SHRUB(INLINED),
+lfsr_data_size(&datas[0])
+ lfsr_data_size(&datas[1]),
DATA(lfsr_data_fromcat(datas, 2)));
coalesce_attrs[1] = LFSR_ATTR(pos
+ lfsr_data_size(&datas[0])
+ lfsr_data_size(&datas[1]),
SHRUB(INLINED),
+weight
- lfsr_data_size(&datas[0])
- lfsr_data_size(&datas[1]),
DATA(datas[2]));
} else {
coalesce_attrs[0] = LFSR_ATTR(pos,
SHRUB(INLINED),
+lfsr_data_size(&datas[0]),
DATA(datas[0]));
if (lfsr_data_size(&datas[1])
+ lfsr_data_size(&datas[2])
// TODO use a different heuristic than cache_size here?
//
// make sure to never write null siblings, even when
// buffer > cache size
<= lfs_max32(lfs->cfg->cache_size, file->buffer_size)) {
coalesce_attrs[1] = LFSR_ATTR(pos + lfsr_data_size(&datas[0]),
SHRUB(INLINED),
+weight - lfsr_data_size(&datas[0]),
DATA(lfsr_data_fromcat(datas + 1, 2)));
} else {
coalesce_attrs[1] = LFSR_ATTR(pos + lfsr_data_size(&datas[0]),
SHRUB(INLINED),
lfsr_data_size(&datas[1]),
DATA(datas[1]));
coalesce_attrs[2] = LFSR_ATTR(pos
+ lfsr_data_size(&datas[0])
+ lfsr_data_size(&datas[1]),
SHRUB(INLINED),
+weight
- lfsr_data_size(&datas[0])
- lfsr_data_size(&datas[1]),
DATA(datas[2]));
}
}
// commit our attributes
int err = lfsr_mdir_commit(lfs, &file->m.mdir, LFSR_ATTRS(
LFSR_ATTR_(file->m.mdir.mid, SHRUBATTRS, 0, SHRUBATTRS(file, LFSR_ATTR_(file->m.mdir.mid, SHRUBATTRS, 0, SHRUBATTRS(file,
// first remove anything in the way // adjust a hole?
LFSR_ATTR( hole_attr,
// remove any data in the way
(lfsr_file_hasshrub(file)
? LFSR_ATTR(
lfs_min32( lfs_min32(
right_pos + lfsr_data_size(&right_data), pos + weight,
file->inlined.u.rbyd.weight)-1, file->inlined.u.rbyd.weight)-1,
RM, RM,
lfs_min32( -(lfs_min32(
right_pos + lfsr_data_size(&right_data), pos + weight,
file->inlined.u.rbyd.weight) - left_pos, file->inlined.u.rbyd.weight)
NULL), - pos),
// TODO this should handling holes somehow NULL)
(lfsr_data_size(&left_data) > 0
? LFSR_ATTR(left_pos,
SHRUB(INLINED),
+lfsr_data_size(&left_data),
DATA(left_data))
: LFSR_ATTR_NOOP), : LFSR_ATTR_NOOP),
LFSR_ATTR(file->buffer_pos, // and then append our data
SHRUB(INLINED), coalesce_attrs[0],
+file->buffer_size, coalesce_attrs[1],
BUF(file->buffer, file->buffer_size)), coalesce_attrs[2]))));
(lfsr_data_size(&right_data) > 0
? LFSR_ATTR(right_pos,
SHRUB(INLINED),
+lfsr_data_size(&right_data),
DATA(right_data))
: LFSR_ATTR_NOOP)))));
if (err) { if (err) {
return err; return err;
} }