schfs: do not read a block off the disk just to overwrite it
kd_write() went through bread(), so every block a write allocated was read from the disk first and then replaced whole. That was a quarter of the disk operations of a SchFS write. bgetblk() is bread() without the read, as Linux's getblk() is. 1 MB on AHCI under QEMU: 2.0 -> 2.8 MB/s in 4 KB writes, 4.5 -> 5.6 MB/s in 64 KB ones. 3 MB of random data written and copied through the cache's evictions reads back with the same md5. The 63 KB/s seen earlier is the legacy IDE driver on QEMU's default machine, at about 10 ms per emulated PIO write; the same disk on AHCI (q35) is 40 times faster. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
This commit is contained in:
parent
7a1e63a553
commit
b213ab5778
3 changed files with 20 additions and 4 deletions
|
|
@ -42,6 +42,7 @@ int blkdev_register(const char *name, const struct blkdev_ops *ops,
|
||||||
// blocks. A filesystem still mounted from it fails on its next access.
|
// blocks. A filesystem still mounted from it fails on its next access.
|
||||||
void blkdev_unregister(int dev);
|
void blkdev_unregister(int dev);
|
||||||
void binval(uint dev); // kernel/dev/bio.c
|
void binval(uint dev); // kernel/dev/bio.c
|
||||||
|
struct buf *bgetblk(uint dev, uint64 blockno); // bread() without the read
|
||||||
|
|
||||||
int blkdev_present(uint dev);
|
int blkdev_present(uint dev);
|
||||||
void blkdev_rw(struct buf *b);
|
void blkdev_rw(struct buf *b);
|
||||||
|
|
|
||||||
|
|
@ -224,6 +224,20 @@ bread(uint dev, uint64 blockno)
|
||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A locked buf for a block the caller is about to overwrite whole, without
|
||||||
|
// reading what is there first: Linux's getblk(). The caller must fill every
|
||||||
|
// byte and then bdirty() it.
|
||||||
|
//
|
||||||
|
// A copy-on-write filesystem writes almost nothing but blocks it has just
|
||||||
|
// allocated, and reading each one off the disk only to replace it was every
|
||||||
|
// fourth disk operation of a SchFS write.
|
||||||
|
struct buf*
|
||||||
|
bgetblk(uint dev, uint64 blockno)
|
||||||
|
{
|
||||||
|
fsstat.bread_calls++;
|
||||||
|
return bget(dev, blockno);
|
||||||
|
}
|
||||||
|
|
||||||
// Write b's contents to disk. Must be locked.
|
// Write b's contents to disk. Must be locked.
|
||||||
void
|
void
|
||||||
bwrite(struct buf *b)
|
bwrite(struct buf *b)
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@
|
||||||
#include "errno.h" /* mounting reports why it failed, not just that it did */
|
#include "errno.h" /* mounting reports why it failed, not just that it did */
|
||||||
#include "x86.h"
|
#include "x86.h"
|
||||||
#include "fsstat.h"
|
#include "fsstat.h"
|
||||||
|
#include "blkdev.h" /* bgetblk */
|
||||||
#include "schfs_int.h"
|
#include "schfs_int.h"
|
||||||
|
|
||||||
// How many buffer-cache blocks one filesystem block spans. BSIZE was 1024 when
|
// How many buffer-cache blocks one filesystem block spans. BSIZE was 1024 when
|
||||||
|
|
@ -392,9 +393,9 @@ kd_write(void *ctx, uint64_t blk, const void *in)
|
||||||
// modifying a block the committed tree can reach, so logging every write
|
// modifying a block the committed tree can reach, so logging every write
|
||||||
// would double the I/O to re-solve a problem the format has already solved.
|
// would double the I/O to re-solve a problem the format has already solved.
|
||||||
//
|
//
|
||||||
// bread before the overwrite because the cache has no "give me this block
|
// bgetblk, not bread: the block is overwritten whole, so what the disk held
|
||||||
// without reading it" entry point. It costs a read the first time a block is
|
// there does not matter. Reading it first cost a disk read for every block
|
||||||
// touched and nothing afterwards.
|
// a write allocated, which is most of them.
|
||||||
//
|
//
|
||||||
// bdirty, not bwrite. The B-tree seals and writes a node on every record it
|
// bdirty, not bwrite. The B-tree seals and writes a node on every record it
|
||||||
// inserts, so a leaf that is collecting one file's checksums was written to
|
// inserts, so a leaf that is collecting one file's checksums was written to
|
||||||
|
|
@ -403,7 +404,7 @@ kd_write(void *ctx, uint64_t blk, const void *in)
|
||||||
// into the last one, and costs nothing in safety because kd_flush below
|
// into the last one, and costs nothing in safety because kd_flush below
|
||||||
// gives the format the one ordering guarantee it actually asks for.
|
// gives the format the one ordering guarantee it actually asks for.
|
||||||
for (int i = 0; i < SCHFS_BPB; i++) {
|
for (int i = 0; i < SCHFS_BPB; i++) {
|
||||||
struct buf *b = bread(d->dev, (uint)(blk * SCHFS_BPB + i));
|
struct buf *b = bgetblk(d->dev, (uint)(blk * SCHFS_BPB + i));
|
||||||
memmove(b->data, (const char *)in + i * BSIZE, BSIZE);
|
memmove(b->data, (const char *)in + i * BSIZE, BSIZE);
|
||||||
bdirty(b);
|
bdirty(b);
|
||||||
brelse(b);
|
brelse(b);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue