Repository navigation
osbench: persist and the disk's cached rows run on ToyOS's DATA, the bench's NVMe - #1097
Conversation
#1097's AMD validation reads persist.fsync_clean at 1.23 ms under ToyOS against 57-70 us under Linux on the same NVMe. Mounted::sync writes the superblock on every commit, so the cache owes a Flush even where nothing changed. #1067's handoff named this open and unfiled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of #1097 at c8f3fbfThe branch has since moved to 8267716, which adds one issue record. I note it at the end. ArchitectureThe design fits. A ToyOS program cannot ask what holds Several things the README states as rules are false or unsupported. That includes two of its four fairness claims: durability and cache state. One of them is contradicted by the PR's own AMD validation. FindingsBLOCKER
BLOCKER
BLOCKER
BLOCKER BLOCKER Evidence: no boot of this head's image has been judged by this head's row.
SUGGESTION Boot medium. No code assumes a stick.
SUGGESTION
SUGGESTION The durability bullet's Linux half also depends on the device reporting a volatile write cache. Recording 8267716 (after the requested head)The new issue matches the evidence: VerdictSEND BACK |
…bench's NVMe #1085 lets ToyOS designate its own DATA, and both benches' fourth partitions are designated and mounted. So the follow-up staged in #1079 lands. ToyOS's persistent filesystem is DATA's /home (bcachefs crate's format, through the DATA file server and the NVMe driver), medium nvme. persist runs its durable rows there, then files' cached operations as fs.disk.*, on both systems: Linux on its home, ext4 on the same NVMe. A row whose media differ still has no verdict, and these now agree. The osbench row refuses a boot whose DATA was not a TOYOS-DATA partition the NVMe driver serves, mounted and not absent. It also refuses the boot that formats a newly designated DATA as a run, stating the format's duration. ToyOS's files group keeps /tmp alone: /home is fs.disk now. The compare's setup reads fs.disk.served_by. The README states the disk rows' rules: - one NVMe for both; - each system's own filesystem, Linux's mount options recorded; - one durability: a sync returns after the device's cache is flushed, ToyOS's fsync covering its whole volume; - one cache state: every row's files are written in its run and read cached on both. #1085 already judges nvme_leaves_the_benchs_disk_alone against a designated DATA, so that part of the staged patch is superseded and dropped. Nothing here depends on #1095 (ToyOS booting off the internal NVMe); the judge finds DATA by the NVMe driver's partition line, wherever the boot came from. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#1097's AMD validation reads persist.fsync_clean at 1.23 ms under ToyOS against 57-70 us under Linux on the same NVMe. Mounted::sync writes the superblock on every commit, so the cache owes a Flush even where nothing changed. #1067's handoff named this open and unfiled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…and the rows' debts are filed Rebased on #1095 (370d7d9). - B1: Linux's fs.disk.mount now carries the superblock options too, where ext4's nobarrier would be, and served_by names the device's write-cache mode (queue/write_cache). - B2: the README states the cache rule as procedure. Every row reads only files it wrote in its run, and neither cache is dropped. ToyOS's write rows include its 32 MiB and 2 s write-back. The 4 KiB sequential read's 158 MB/s is filed: a host probe of the cache shows a file 16 blocks over CLEAN_LIMIT missing every block on every pass (16,400 requests) and one 16 under missing none, the insertion-order eviction. - B3: the README says the compare refuses differing media, and that ToyOS's medium is a constant the osbench row alone makes true. - B4: the rewrap. - The osbench row's judge is hostdisk::left_alone with the loader's volumes: Mounted passes, Formatted is the format boot (its duration stated), and Foreign or memory is refused. The second parser goes. - The README says ToyOS's log volume shares the NVMe, and the image comment says the image, not the stick, has no DATA partition. - issues/a-file-server-request-costs-ten-times-a-kernel-file-call.md: the cached fs.disk rows' 4-12x, the round trip, against Linux and ToyOS's own /tmp, with parity as its exit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From #1097's T14 validation: a clean fsync costs ToyOS 3.0 ms, so its Flush on that disk costs about that, against Linux's whole write and fsync at 0.8-1.0 ms. The AMD's Samsung shows no such gap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
r1 response, head
|
The landing head's T14 validation (069cbbe) read ToyOS's write_4k_fsync at 1.10 ms against Linux's 1.01-1.04 ms, where c8f3fbf's read 6.1 ms. No storage-path code changed between them and both booted off the NVMe, so the record states both boots and an unknown cause instead of a sixfold gap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review r2 of #1097 at 069cbbe, with d6fc8be notedThis round was asked to review 069cbbe. The branch has since moved to d6fc8be, which replaces the T14 record and changes nothing else. Both heads are reviewed below. The metal runs at 069cbbe also cover d6fc8be, because d6fc8be touches only ArchitectureUnchanged since r1, and still a good fit. Moving the judge to r1 blockers
RebaseNothing this PR depends on broke. Between c8f3fbf and 069cbbe, nothing under FindingsBLOCKER (resolved at d6fc8be) BLOCKER N1
BLOCKER N2
BLOCKER N3 The evidence branch
BLOCKER N4 (orchestrator's correction) The PR body no longer matches the change or its evidence.
SUGGESTION SUGGESTION VerdictSEND BACK. N1–N3 are wording or republishing fixes that need no new runs. N4 belongs to the orchestrator. |
- N1: the T14 durable-write record drops "followed DATA's format by less than a day", which both boots did, and says Linux's clean fsync takes 23 us where it claimed Linux issues no write or Flush, which nothing measured. - N2: the file-server record is "three to twelve times", the ratios its table shows. Its /tmp column is the same boot's. "The disk adds nothing" holds for four rows, and open_close's 32% on the NVMe is stated as unattributed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The last disk row ToyOS loses at #1097's landing head without a record: 8-22% dearer on both benches, where the plain durable writes are at parity. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Orchestrator: r2's N1–N4 checked against d6fc8be..ddc26a3 (issue records only, so the landing-head runs at 069cbbe still apply). N1: the T14 record states the measured 23 µs and drops the format-age candidate. N2: the record is renamed to three to twelve times, with both columns from one boot and open_close's +32% stated. N3: the full probe patch is republished at 38db2e75 with a correction note. N4: the body describes the final change and pins r4. The new create_fsync_unlink record states only its measured rows. Landing. |
The cache kept at most 16,384 clean blocks, 64 MiB, and drops the oldest read first without moving a block it hits. osbench's fs.disk.seq_read.4096 rereads a 64 MiB file: its blocks and the tree nodes an open reads come to one more than the cache kept, so each fetched run evicted the blocks the reader reached next and every pass came off the disk whole. #1097's AMD readback has 158 MB/s p50 against 4,960-5,054 under Linux. Why one round of five read 426 MB/s is not shown; dirty blocks are outside the clean count, so a round run before the write-back would fit. A host test written as the benchmark reads (write 64 MiB in 4 KiB calls, sync, then open, read whole and close in rounds) read 16,385 blocks off the disk in each round after the first at the old limit. It now runs on a cache holding twice the file and reads none. Each cache is made with its limit, and the server gives it an eighth of physical memory: the share netstack and the compositor take for the same reason, that nothing signals memory pressure, so the bound is all that keeps a cache from holding the machine. The eviction order is unchanged, so a file larger than that still misses on every pass, which #1097's a-sequential-read-of-a-file-as-large-as-datas-clean-cache-reads-the-disk.md records. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mounted::sync committed unconditionally: it flushed the device, wrote both superblock copies and flushed again. Under the file server's cache the first flush costs nothing when nothing is dirty, but the superblock write always left one owed, so every fsync on DATA paid two block writes and an NVMe Flush. #1097's AMD readback has persist.fsync_clean at 1.23 ms against 57-70 us under Linux on the same disk. A mount now keeps the superblock its last commit made durable. A commit whose superblock equals it, the allocation cursor aside, writes none: under shadowing no node the landed root reaches is written over, so an equal root is the landed tree. The device is still flushed first, for the data pages and bitmap blocks written in place, so a sync still returns only once everything written before it is on the medium. The cursor is excluded because a commit's own frees move it back after its superblock lands, and where the next scan starts is no reason to commit. Nothing is known landed at mount, after mkfs or after a refused sync, so each of those commits whole. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s across five boots The 2026-10-28 scoreboard compares ToyOS at 069cbbe against each bench's Linux as installed: 4 boots against 5 runs, interleaved (evidence branch scoreboard-2026-10-28). Each row ToyOS loses with no owning record now has one, with its figures, an owner and an exit: - a small message's round trip over a native connection (ipc.reqrep.8, 2.04x on the T14); - the futex ping-pong (2.01x and 1.37x on the T14); - tmpfs metadata calls (fs.mem open_close, stat, rename, mkdir_rmdir, 4.1-6.5x on the T14); - process and thread spawn, and the all-CPU build (up to 3.7x on the T14, and 2.3x for j16 on the AMD); - small memcpy on the AMD (64 B at 0.61 of Linux's); - no loopback, so osbench net measures nothing; - std's yield_now, which yields nothing. The T14 durable-write record takes all five boots: 2 of 5 ran persist six times slower throughout and a third turned slow partway, with nothing in the logs to tell them apart. It gets a slug that says so, which #1097 had on its branch after it merged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cache kept at most 16,384 clean blocks, 64 MiB, and drops the oldest read first without moving a block it hits. osbench's fs.disk.seq_read.4096 rereads a 64 MiB file: its blocks and the tree nodes an open reads come to one more than the cache kept, so each fetched run evicted the blocks the reader reached next and every pass came off the disk whole. #1097's AMD readback has 158 MB/s p50 against 4,960-5,054 under Linux. Why one round of five read 426 MB/s is not shown; dirty blocks are outside the clean count, so a round run before the write-back would fit. A host test written as the benchmark reads (write 64 MiB in 4 KiB calls, sync, then open, read whole and close in rounds) read 16,385 blocks off the disk in each round after the first at the old limit. It now runs on a cache holding twice the file and reads none. Each cache is made with its limit, and the server gives it an eighth of physical memory: the share netstack and the compositor take for the same reason, that nothing signals memory pressure, so the bound is all that keeps a cache from holding the machine. The eviction order is unchanged, so a file larger than that still misses on every pass, which #1097's a-sequential-read-of-a-file-as-large-as-datas-clean-cache-reads-the-disk.md records. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each server took an eighth of physical memory, so the DATA, log and boot servers together could keep three eighths clean, and writes fill a cache as well as reads: a flushed block stays as a clean one. The eighth netstack and the compositor take rests on leaving seven eighths to everyone else, which three such shares do not. Now the log and boot roles keep 16 MiB each, their FAT and directories, and DATA the rest of one eighth. The interim, that nothing gives a clean block back to another job, is the-file-servers-clean-blocks-are-given-back-to-no-other-job.md, owned with the memory track: its exit is clean blocks dropped under the memory service's pressure and CLEAN_SHARE deleted. A block read in is cached only if the allocator gives it room. Box::new cannot fail, so a miss under another job's exhaustion aborted the server and lost what it held dirty; a refused clean block is now just not kept, and the read is answered from the disk's bytes. The total-memory decode lives in the SDK (toyos::system::memory_total) for the file server, netstack and the compositor, which each had a copy. #1097's record of the eviction cliff named CLEAN_LIMIT, which is gone; it now names the server's limit, and stays open. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mounted::sync committed unconditionally: it flushed the device, wrote both superblock copies and flushed again. Under the file server's cache the first flush costs nothing when nothing is dirty, but the superblock write always left one owed, so every fsync on DATA paid two block writes and an NVMe Flush. #1097's AMD readback has persist.fsync_clean at 1.23 ms against 57-70 us under Linux on the same disk. A mount now keeps the superblock its last commit made durable. A commit whose superblock equals it, the allocation cursor aside, writes none: under shadowing no node the landed root reaches is written over, so an equal root is the landed tree. The device is still flushed first, for the data pages and bitmap blocks written in place, so a sync still returns only once everything written before it is on the medium. The cursor is excluded because a commit's own frees move it back after its superblock lands, and where the next scan starts is no reason to commit. Nothing is known landed at mount, after mkfs or after a refused sync, so each of those commits whole. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cache kept at most 16,384 clean blocks, 64 MiB, and drops the oldest read first without moving a block it hits. osbench's fs.disk.seq_read.4096 rereads a 64 MiB file: its blocks and the tree nodes an open reads come to one more than the cache kept, so each fetched run evicted the blocks the reader reached next and every pass came off the disk whole. #1097's AMD readback has 158 MB/s p50 against 4,960-5,054 under Linux. Why one round of five read 426 MB/s is not shown; dirty blocks are outside the clean count, so a round run before the write-back would fit. A host test written as the benchmark reads (write 64 MiB in 4 KiB calls, sync, then open, read whole and close in rounds) read 16,385 blocks off the disk in each round after the first at the old limit. It now runs on a cache holding twice the file and reads none. Each cache is made with its limit, and the server gives it an eighth of physical memory: the share netstack and the compositor take for the same reason, that nothing signals memory pressure, so the bound is all that keeps a cache from holding the machine. The eviction order is unchanged, so a file larger than that still misses on every pass, which #1097's a-sequential-read-of-a-file-as-large-as-datas-clean-cache-reads-the-disk.md records. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each server took an eighth of physical memory, so the DATA, log and boot servers together could keep three eighths clean, and writes fill a cache as well as reads: a flushed block stays as a clean one. The eighth netstack and the compositor take rests on leaving seven eighths to everyone else, which three such shares do not. Now the log and boot roles keep 16 MiB each, their FAT and directories, and DATA the rest of one eighth. The interim, that nothing gives a clean block back to another job, is the-file-servers-clean-blocks-are-given-back-to-no-other-job.md, owned with the memory track: its exit is clean blocks dropped under the memory service's pressure and CLEAN_SHARE deleted. A block read in is cached only if the allocator gives it room. Box::new cannot fail, so a miss under another job's exhaustion aborted the server and lost what it held dirty; a refused clean block is now just not kept, and the read is answered from the disk's bytes. The total-memory decode lives in the SDK (toyos::system::memory_total) for the file server, netstack and the compositor, which each had a copy. #1097's record of the eviction cliff named CLEAN_LIMIT, which is gone; it now names the server's limit, and stays open. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ToyOS's persistent rows run on the bench's NVMe. #1079 staged this patch until ToyOS could designate its own DATA, which #1085 brought. Rebased on #1095 (
370d7d9f6), so ToyOS boots off the same NVMe.Problem and result
In #1079,
persistcompared ToyOS's boot stick with Linux's NVMe and read "the media differ". Now ToyOS's persistent filesystem is DATA's/home: the bench's TOYOS-DATA partition, through the DATA file server and the NVMe driver, mediumnvme. Linux's is its home, ext4 on the same NVMe namespace.persistruns the durable rows and thenfiles' operations asfs.disk.*, on both systems, so those rows can resolve. This is the disk and filesystem scoreboard toward Linux parity.Changes
osbench:persistent_diris/homewith mediumnvme, a constant that the metal row alone makes true;storageruns durable rows, thenfiles::suite(disk);fileskeeps/tmp;fs.disk.mountcarries the superblock options, andserved_bynames the device's write-cache mode;SETUPreadsfs.disk.served_by.The
osbenchmetal row judges DATA withhostdisk::left_aloneand the loader's volumes:Mountedpasses;Formattedis the format boot, no measured run, with its duration stated;Foreignor memory is refused.README: the disk rows' rules, as procedure:
It also says that ToyOS's log volume shares the NVMe.
Issues: each disk row ToyOS loses, with its evidence:
a-clean-fsync-on-data-writes-the-superblock-and-flushes-the-disk;a-sequential-read-of-a-file-as-large-as-datas-clean-cache-reads-the-disk, whose host probe confirms the cause;a-file-server-request-costs-three-to-twelve-times-a-kernel-file-call;toyos-durable-writes-on-the-t14s-nvme-run-six-times-slower-in-some-boots;a-created-fsynced-and-unlinked-file-costs-toyos-more-than-linux.The comparison record and the 64 MiB record are updated.
nvme_leaves_the_benchs_disk_alone's designated premise came with #1085, so it is not in this PR.What the landing head's validation shows (
069cbbeb8)One ToyOS boot and two Linux runs per bench. Each was judged PASS, exit 0, from a tree pinned at
069cbbeb8(r4). So no row resolves yet; the scoreboard's further pairs are where these become verdicts.persist.fsync_cleanMounted::syncwrites the superblock, so it costs a block write and a Flush. Linux's clean fsync takes tens of µs.persist.write_4k_fsyncpersist.append_4k_fsyncpersist.create_fsync_unlinkpersist.seq_write_fsyncfs.disk.read_4k_atand the other cached ops/tmp.fs.disk.seq_read.4096CLEAN_LIMIT; a host probe shows every pass missing.Setup, and why it explains none of these:
tscin every landing-head run.persistcompleted.Checks
Evidence: index.
cargo run -- --ci hostatb4121ccc0: exit 0, 105 steps green. Every commit since (069cbbeb8to the head) changes only issue records.osbenchrow atb4121ccc0:c8f3fbfb0boot through toyos-metal boots every bench image off the bench's internal NVMe #1095's runner: PASS;fa7258221designated boot: PASS;069cbbeb8: run on both benches, Linux, ToyOS, Linux, and the judge passed on each, exit 0 (r4). Thec8f3fbfb0runs passed too (r2, r3).98d8b7f2…f796; both musl targets check.Risks
write_4k_fsync,append_4k_fsync) on both benches, and loses every other disk row. Each loss is filed; this PR measures them and doesn't fix them.🤖 Generated with Claude Code