schfs: cache sixteen inodes, not one
The one-entry cache lived inside schfs_file_read, so stat() never touched it -- which is why `ls` barely moved. Moved into schfs_inode_get, where every caller benefits, and widened to sixteen entries. `ls` is the shape that matters: it stats every name in a directory, so a single entry never hits and each file costs two descents, one to find the name and one to read the inode. Sixteen keeps hitting through a directory walk without a second reader evicting everything. Round-robin rather than LRU: the pattern is a walk, not a working set, so the bookkeeping on every hit would buy nothing. Measured in the guest, TCG, against the original baseline: ls /bin warm 0.70s -> 0.43s ls /bin cold 2.80s -> 1.32s read 834 KB 1.73s -> 0.35s schfs-check passes including the round-trip; bootcheck 108 [ok]. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
ddf1e41177
commit
bbb61c7405
1 changed files with 47 additions and 20 deletions
|
|
@ -80,10 +80,43 @@ schfs_namehash(const char *name, unsigned len)
|
|||
/* Inodes */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/* Inode cache.
|
||||
*
|
||||
* Sixteen entries, not one. `ls` is the shape that matters: it stats every name
|
||||
* in a directory, so a single entry never hits and the whole directory costs
|
||||
* two tree descents per file -- one to find the name, one to read the inode.
|
||||
* Sixteen is enough that a directory walk keeps hitting while a second reader
|
||||
* elsewhere does not evict everything.
|
||||
*
|
||||
* Round-robin replacement, deliberately: an LRU needs bookkeeping on every hit,
|
||||
* and the access pattern here is a walk rather than a working set, so the
|
||||
* cleverer policy buys nothing.
|
||||
*
|
||||
* Invalidation is blunt and total -- see schfs_cache_invalidate().
|
||||
*/
|
||||
#define INO_CACHE_N 16
|
||||
|
||||
static struct {
|
||||
int valid;
|
||||
const struct schfs_vol *vol;
|
||||
uint64_t ino;
|
||||
struct schfs_inode in;
|
||||
} ino_cache[INO_CACHE_N];
|
||||
|
||||
static unsigned ino_cache_next;
|
||||
|
||||
int
|
||||
schfs_inode_get(struct schfs_vol *v, struct schfs_cursor *c,
|
||||
uint64_t ino, struct schfs_inode *out)
|
||||
{
|
||||
for (unsigned i = 0; i < INO_CACHE_N; i++) {
|
||||
if (ino_cache[i].valid && ino_cache[i].vol == v &&
|
||||
ino_cache[i].ino == ino) {
|
||||
*out = ino_cache[i].in;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
struct schfs_key k;
|
||||
schfs_mkkey(&k, ino, SCHFS_KEY_INODE, 0);
|
||||
|
||||
|
|
@ -96,6 +129,12 @@ schfs_inode_get(struct schfs_vol *v, struct schfs_cursor *c,
|
|||
// it rather than acting on a half-filled struct.
|
||||
if (len != sizeof(*out))
|
||||
return -2;
|
||||
|
||||
unsigned slot = ino_cache_next++ % INO_CACHE_N;
|
||||
ino_cache[slot].valid = 1;
|
||||
ino_cache[slot].vol = v;
|
||||
ino_cache[slot].ino = ino;
|
||||
ino_cache[slot].in = *out;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -287,7 +326,7 @@ schfs_dir_iterate(struct schfs_vol *v, struct schfs_cursor *c, uint64_t dir,
|
|||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Read caches: the last inode, and the last extent.
|
||||
* Read caches: recently-read inodes (see schfs_inode_get) and the last extent.
|
||||
*
|
||||
* Every read used to descend the B-tree twice per block -- once for the inode
|
||||
* and once for the extent -- so an 834 KB file cost 418 descents, each a tree
|
||||
|
|
@ -307,13 +346,6 @@ schfs_dir_iterate(struct schfs_vol *v, struct schfs_cursor *c, uint64_t dir,
|
|||
* The volume pointer is part of the key because the host tools can hold more
|
||||
* than one volume open, and two images would otherwise share these.
|
||||
* ------------------------------------------------------------------------ */
|
||||
static struct {
|
||||
int valid;
|
||||
const struct schfs_vol *vol;
|
||||
uint64_t ino;
|
||||
struct schfs_inode in;
|
||||
} ino_cache;
|
||||
|
||||
static struct {
|
||||
int valid;
|
||||
const struct schfs_vol *vol;
|
||||
|
|
@ -326,7 +358,8 @@ static struct {
|
|||
void
|
||||
schfs_cache_invalidate(void)
|
||||
{
|
||||
ino_cache.valid = 0;
|
||||
for (unsigned i = 0; i < INO_CACHE_N; i++)
|
||||
ino_cache[i].valid = 0;
|
||||
ext_cache.valid = 0;
|
||||
}
|
||||
|
||||
|
|
@ -412,18 +445,12 @@ long
|
|||
schfs_file_read(struct schfs_vol *v, struct schfs_cursor *c, uint64_t ino,
|
||||
uint64_t off, void *buf, unsigned n, void *dblk)
|
||||
{
|
||||
/* schfs_inode_get caches for itself now, so this is a hit on every chunk
|
||||
* after the first without a second cache here to keep coherent. */
|
||||
struct schfs_inode in;
|
||||
if (ino_cache.valid && ino_cache.vol == v && ino_cache.ino == ino) {
|
||||
in = ino_cache.in;
|
||||
} else {
|
||||
int r = schfs_inode_get(v, c, ino, &in);
|
||||
if (r != 0)
|
||||
return r;
|
||||
ino_cache.valid = 1;
|
||||
ino_cache.vol = v;
|
||||
ino_cache.ino = ino;
|
||||
ino_cache.in = in;
|
||||
}
|
||||
int r = schfs_inode_get(v, c, ino, &in);
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
if (off >= in.size)
|
||||
return 0;
|
||||
|
|
|
|||
Loading…
Reference in a new issue