diff --git a/kernel/fs/schfs/obj.c b/kernel/fs/schfs/obj.c index 7cb74ae..1fa45b1 100644 --- a/kernel/fs/schfs/obj.c +++ b/kernel/fs/schfs/obj.c @@ -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;