Reworked the read path to use a single flush
The motivation for this comes from the observation that lfs3_file_flush
already implies lfs3_file_crystallize, so most of the time the isuncryst
check in lfs3_file_readnext is useless.
We _do_ hit the isuncryst check when bypassing the cache, but the
situation where we bypass the cache, on a read-write file, _and_ can
avoid crystallization, seems too niche to care about.
So this reworks lfs3_file_read to prevent cache bypassing until pending
data is at least crystallized. This mirrors how we force flushing in
lfs3_file_write.
lfs3_file_read:
|<------------------------------------------------------------.
v |
data in cache? --> read from cache -------------------------------->|
| n y |
v |
data in btree? --> crystallized? --> bypass? --> read from disk --->|
| n y | n y | n y |
| | v |
| | flushed? --> read into cache ->|
| | | n y |
| | v |
| '-------> flush cache -------------------->|
v |
fill with zeros ----------------------------------------------------'
lfs3_file_write:
|<------------------------------------.
v |
flushed? --> bypass? --> write to disk ->|
| n y | n y |
| v |
| move cache |
v v |
aligned? --> write into cache ---------->|
| n y |
v |
flush cache -----------------------------'
---
As a part of the rework, I also manually inlined lfs3_file_readnext into
lfs3_file_readget_. This duplicates some logic (not code cost!), but
helps clean up some of the ifdef soup in lfs3_file_readnext.
I also tried to refactor lfs3_file_readnext to better match
lfs3_file_read and lfs3_file_write's logic, but I'm not it actually
gained us anything.
lfs3_file_readnext:
|<----------------------------.
v |
data in leaf? --> read from leaf |
| n y | |
v v |
data in hole? --> fill with zeros |
| n y | |
v | |
fetch leaf -------------|----------'
v
done!
Saves a bit of code:
code stack ctx
before: 38060 2456 656
after: 38020 (-0.1%) 2456 (+0.0%) 656 (+0.0%)
Also likely eliminates lfs3_file_readnext from ever becoming the stack
hot-path again.
This commit is contained in:
@@ -12074,92 +12074,69 @@ static int lfs3_file_lookupnext(lfs3_t *lfs3, const lfs3_file_t *file,
|
|||||||
// needed in lfs3_file_readnext
|
// needed in lfs3_file_readnext
|
||||||
static int lfs3_file_crystallize(lfs3_t *lfs3, lfs3_file_t *file);
|
static int lfs3_file_crystallize(lfs3_t *lfs3, lfs3_file_t *file);
|
||||||
|
|
||||||
|
#ifndef LFS3_KVONLY
|
||||||
static lfs3_ssize_t lfs3_file_readnext(lfs3_t *lfs3, lfs3_file_t *file,
|
static lfs3_ssize_t lfs3_file_readnext(lfs3_t *lfs3, lfs3_file_t *file,
|
||||||
lfs3_off_t pos, uint8_t *buffer, lfs3_size_t size) {
|
lfs3_off_t pos, uint8_t *buffer, lfs3_size_t size) {
|
||||||
// hit our leaf?
|
// the leaf must not be pinned down here
|
||||||
lfs3_bid_t bid;
|
LFS3_ASSERT(!lfs3_o_isuncryst(file->b.o.flags));
|
||||||
lfs3_bid_t weight;
|
LFS3_ASSERT(!lfs3_o_isungraft(file->b.o.flags));
|
||||||
lfs3_bptr_t bptr;
|
|
||||||
if (LFS3_IFDEF_KVONLY(
|
|
||||||
false,
|
|
||||||
pos >= file->leaf.pos
|
|
||||||
&& pos < file->leaf.pos + file->leaf.weight)) {
|
|
||||||
#ifndef LFS3_KVONLY
|
|
||||||
bid = file->leaf.pos + (file->leaf.weight-1);
|
|
||||||
weight = file->leaf.weight;
|
|
||||||
bptr = file->leaf.bptr;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// need to fetch a new leaf?
|
while (true) {
|
||||||
} else {
|
// any data in our leaf?
|
||||||
// leaf in use? we need to crystallize/graft it
|
if (pos >= file->leaf.pos
|
||||||
//
|
&& pos < file->leaf.pos + file->leaf.weight) {
|
||||||
// it would be easier to just call lfs3_file_flush here, but
|
// any data on disk?
|
||||||
// we don't want to drag in the extra stack usage
|
lfs3_off_t pos_ = pos;
|
||||||
#if !defined(LFS3_RDONLY) \
|
if (pos_ < file->leaf.pos + lfs3_bptr_size(&file->leaf.bptr)) {
|
||||||
&& !defined(LFS3_KVONLY) \
|
// note one important side-effect here is a strict
|
||||||
&& !defined(LFS3_2BONLY)
|
// data hint
|
||||||
if (lfs3_o_isungraft(file->b.o.flags)
|
lfs3_ssize_t d = lfs3_min(
|
||||||
|| lfs3_o_isuncryst(file->b.o.flags)) {
|
size,
|
||||||
// readonly files can't be uncrystallized/ungrafted
|
lfs3_bptr_size(&file->leaf.bptr)
|
||||||
LFS3_ASSERT(!lfs3_o_isrdonly(file->b.o.flags));
|
- (pos_ - file->leaf.pos));
|
||||||
|
lfs3_data_t slice = LFS3_DATA_SLICE(file->leaf.bptr.data,
|
||||||
|
pos_ - file->leaf.pos,
|
||||||
|
d);
|
||||||
|
d = lfs3_data_read(lfs3, &slice,
|
||||||
|
buffer, d);
|
||||||
|
if (d < 0) {
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
int err = lfs3_file_crystallize(lfs3, file);
|
pos_ += d;
|
||||||
if (err) {
|
buffer += d;
|
||||||
return err;
|
size -= d;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// fetch a leaf
|
// found a hole? fill with zeros
|
||||||
|
lfs3_ssize_t d = lfs3_min(
|
||||||
|
size,
|
||||||
|
file->leaf.pos+file->leaf.weight - pos_);
|
||||||
|
lfs3_memset(buffer, 0, d);
|
||||||
|
|
||||||
|
pos_ += d;
|
||||||
|
buffer += d;
|
||||||
|
size -= d;
|
||||||
|
|
||||||
|
return pos_ - pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch a new leaf
|
||||||
|
lfs3_bid_t bid;
|
||||||
|
lfs3_bid_t weight;
|
||||||
|
lfs3_bptr_t bptr;
|
||||||
int err = lfs3_file_lookupnext_(lfs3, file, pos,
|
int err = lfs3_file_lookupnext_(lfs3, file, pos,
|
||||||
&bid, &weight, &bptr);
|
&bid, &weight, &bptr);
|
||||||
if (err) {
|
if (err) {
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef LFS3_KVONLY
|
|
||||||
file->leaf.pos = bid - (weight-1);
|
file->leaf.pos = bid - (weight-1);
|
||||||
file->leaf.weight = weight;
|
file->leaf.weight = weight;
|
||||||
file->leaf.bptr = bptr;
|
file->leaf.bptr = bptr;
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// any data on disk?
|
|
||||||
lfs3_off_t pos_ = pos;
|
|
||||||
if (pos_ < bid-(weight-1) + lfs3_bptr_size(&bptr)) {
|
|
||||||
// note one important side-effect here is a strict
|
|
||||||
// data hint
|
|
||||||
lfs3_ssize_t d = lfs3_min(
|
|
||||||
size,
|
|
||||||
lfs3_bptr_size(&bptr)
|
|
||||||
- (pos_ - (bid-(weight-1))));
|
|
||||||
lfs3_data_t slice = LFS3_DATA_SLICE(bptr.data,
|
|
||||||
pos_ - (bid-(weight-1)),
|
|
||||||
d);
|
|
||||||
d = lfs3_data_read(lfs3, &slice,
|
|
||||||
buffer, d);
|
|
||||||
if (d < 0) {
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
|
|
||||||
pos_ += d;
|
|
||||||
buffer += d;
|
|
||||||
size -= d;
|
|
||||||
}
|
|
||||||
|
|
||||||
// found a hole? fill with zeros
|
|
||||||
lfs3_ssize_t d = lfs3_min(
|
|
||||||
size,
|
|
||||||
bid+1 - pos_);
|
|
||||||
lfs3_memset(buffer, 0, d);
|
|
||||||
|
|
||||||
pos_ += d;
|
|
||||||
buffer += d;
|
|
||||||
size -= d;
|
|
||||||
|
|
||||||
return pos_ - pos;
|
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// high-level file reading
|
// high-level file reading
|
||||||
|
|
||||||
@@ -12176,16 +12153,47 @@ static lfs3_ssize_t lfs3_file_readget_(lfs3_t *lfs3, lfs3_file_t *file,
|
|||||||
uint8_t *buffer_ = buffer;
|
uint8_t *buffer_ = buffer;
|
||||||
while (size > 0 && pos_ < lfs3_file_size_(file)) {
|
while (size > 0 && pos_ < lfs3_file_size_(file)) {
|
||||||
// read from the bshrub/btree
|
// read from the bshrub/btree
|
||||||
lfs3_ssize_t d_ = lfs3_file_readnext(lfs3, file,
|
lfs3_bid_t bid;
|
||||||
pos_, buffer_, size);
|
lfs3_bid_t weight;
|
||||||
if (d_ < 0) {
|
lfs3_bptr_t bptr;
|
||||||
LFS3_ASSERT(d_ != LFS3_ERR_NOENT);
|
int err = lfs3_file_lookupnext_(lfs3, file, pos_,
|
||||||
return d_;
|
&bid, &weight, &bptr);
|
||||||
|
if (err) {
|
||||||
|
LFS3_ASSERT(err != LFS3_ERR_NOENT);
|
||||||
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
pos_ += d_;
|
// any data on disk?
|
||||||
buffer_ += d_;
|
if (pos_ < bid-(weight-1) + lfs3_bptr_size(&bptr)) {
|
||||||
size -= d_;
|
// note one important side-effect here is a strict
|
||||||
|
// data hint
|
||||||
|
lfs3_ssize_t d = lfs3_min(
|
||||||
|
size,
|
||||||
|
lfs3_bptr_size(&bptr)
|
||||||
|
- (pos_ - (bid-(weight-1))));
|
||||||
|
lfs3_data_t slice = LFS3_DATA_SLICE(bptr.data,
|
||||||
|
pos_ - (bid-(weight-1)),
|
||||||
|
d);
|
||||||
|
d = lfs3_data_read(lfs3, &slice,
|
||||||
|
buffer_, d);
|
||||||
|
if (d < 0) {
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
pos_ += d;
|
||||||
|
buffer_ += d;
|
||||||
|
size -= d;
|
||||||
|
}
|
||||||
|
|
||||||
|
// found a hole? fill with zeros
|
||||||
|
lfs3_ssize_t d = lfs3_min(
|
||||||
|
size,
|
||||||
|
bid+1 - pos_);
|
||||||
|
lfs3_memset(buffer_, 0, d);
|
||||||
|
|
||||||
|
pos_ += d;
|
||||||
|
buffer_ += d;
|
||||||
|
size -= d;
|
||||||
}
|
}
|
||||||
|
|
||||||
// return amount read
|
// return amount read
|
||||||
@@ -12231,43 +12239,47 @@ lfs3_ssize_t lfs3_file_read(lfs3_t *lfs3, lfs3_file_t *file,
|
|||||||
|
|
||||||
// any data in our btree?
|
// any data in our btree?
|
||||||
if (pos_ < lfs3_file_weight_(file)) {
|
if (pos_ < lfs3_file_weight_(file)) {
|
||||||
// bypass cache?
|
if (!lfs3_o_isuncryst(file->b.o.flags)
|
||||||
if ((lfs3_size_t)d >= lfs3_file_cachesize(lfs3, file)) {
|
&& !lfs3_o_isungraft(file->b.o.flags)) {
|
||||||
lfs3_ssize_t d_ = lfs3_file_readnext(lfs3, file,
|
// bypass cache?
|
||||||
pos_, buffer_, d);
|
if ((lfs3_size_t)d >= lfs3_file_cachesize(lfs3, file)) {
|
||||||
if (d_ < 0) {
|
lfs3_ssize_t d_ = lfs3_file_readnext(lfs3, file,
|
||||||
LFS3_ASSERT(d_ != LFS3_ERR_NOENT);
|
pos_, buffer_, d);
|
||||||
return d_;
|
if (d_ < 0) {
|
||||||
|
LFS3_ASSERT(d_ != LFS3_ERR_NOENT);
|
||||||
|
return d_;
|
||||||
|
}
|
||||||
|
|
||||||
|
pos_ += d_;
|
||||||
|
buffer_ += d_;
|
||||||
|
size -= d_;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
pos_ += d_;
|
// try to fill our cache with some data
|
||||||
buffer_ += d_;
|
if (!lfs3_o_isunflush(file->b.o.flags)) {
|
||||||
size -= d_;
|
lfs3_ssize_t d_ = lfs3_file_readnext(lfs3, file,
|
||||||
continue;
|
pos_, file->cache.buffer, d);
|
||||||
|
if (d_ < 0) {
|
||||||
|
LFS3_ASSERT(d != LFS3_ERR_NOENT);
|
||||||
|
return d_;
|
||||||
|
}
|
||||||
|
file->cache.pos = pos_;
|
||||||
|
file->cache.size = d_;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// cache in use? we need to flush it
|
// flush our cache so the above can't fail
|
||||||
//
|
//
|
||||||
// note that flush does not change the actual file data, so if
|
// note that flush does not change the actual file data, so if
|
||||||
// a read fails it's ok to fall back to our flushed state
|
// a read fails it's ok to fall back to our flushed state
|
||||||
//
|
//
|
||||||
if (lfs3_o_isunflush(file->b.o.flags)) {
|
int err = lfs3_file_flush(lfs3, file);
|
||||||
int err = lfs3_file_flush(lfs3, file);
|
if (err) {
|
||||||
if (err) {
|
return err;
|
||||||
return err;
|
|
||||||
}
|
|
||||||
lfs3_file_discardcache(file);
|
|
||||||
}
|
}
|
||||||
|
lfs3_file_discardcache(file);
|
||||||
// try to fill our cache with some data
|
|
||||||
lfs3_ssize_t d_ = lfs3_file_readnext(lfs3, file,
|
|
||||||
pos_, file->cache.buffer, d);
|
|
||||||
if (d_ < 0) {
|
|
||||||
LFS3_ASSERT(d != LFS3_ERR_NOENT);
|
|
||||||
return d_;
|
|
||||||
}
|
|
||||||
file->cache.pos = pos_;
|
|
||||||
file->cache.size = d_;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user