diskbench narrowed its offset to off_t, so 8 GB of file was 2 GiB of reads - #43
Open
mfethe1 wants to merge 1 commit into
Open
diskbench narrowed its offset to off_t, so 8 GB of file was 2 GiB of reads#43mfethe1 wants to merge 1 commit into
mfethe1 wants to merge 1 commit into
Conversation
…reads waste_pread takes an int64_t offset, and on Windows splits it into ReadFile's Offset/OffsetHigh pair, precisely so a read past 2 GiB works. The random-read loop handed it an off_t instead. Under LLP64 — MSYS2 UCRT64, the documented Windows build — off_t is a 32-bit long while g_file and g_rec are size_t, so `(off_t)(seed % nrec) * g_rec` truncates. The default file size is 8 GB, so this was the default path, not a corner. Two failure modes, and the quiet one is worse. Offsets that wrapped negative were refused by waste_pread's `off < 0` guard, which returns -1 without touching errno; the loop prints "short read -1: No error" and breaks, so that thread stops early and the bytes it never read still divide into its elapsed time. Offsets that wrapped positive did not fail at all — they read the wrong place, successfully. A run kept its whole working set inside the first 2 GiB while reporting the size it was asked for, and a 2 GiB hot region sits inside a consumer SSD's SLC cache, which is exactly the flattery this tool exists to avoid. docs/GATES.md Gate H sized the storage premise on this program. Measured on Windows 11 / Ryzen 7 3700X / MSYS2 UCRT64 / gcc 16.2.0, WD_BLACK SN7100 4 TB. Same path, same parameters, the two runs back to back: diskbench PATH 8 12 4 before after short reads 7 x "-1: No error" none rand 1 thr 0.36 GB/s 1.48 GB/s rand 2 thr 0.11 GB/s 2.23 GB/s rand 4 thr 0.06 GB/s 2.25 GB/s seq write / seq read 2.25 / 2.17 2.21 / 2.13 The sequential columns barely move, which is what makes the pair comparable — only the random path changed. The before column is not a slow disk and not even a stable wrong answer: it *inverts* with thread count, because each thread breaks out of its loop at whatever offset first truncates, and more threads means that happens sooner. An earlier run of the same unmodified binary gave 0.34 / 0.80 / 1.02; the garbage is not reproducible, which is its own argument. At 2 GB, where nothing truncates, it reports a clean monotonic 1.58 / 2.00 / 2.33. Absolute throughput here is depressed by an unrelated model conversion running on the same drive. That load is common to both columns and the sequential rows show it; the numbers are a before/after pair, not a device rating. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B5eVMiauR4Lkt8Dhc67MNb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
waste_preadtakes anint64_toffset, and on Windows splits it intoReadFile'sOffset/OffsetHighpair (src/platform.h:189-198) precisely so a read past 2 GiB works.tools/diskbench.c:250handed it anoff_tinstead:Under LLP64 — MSYS2 UCRT64, the documented Windows build path (#36) —
off_tis a 32-bitlong, whileg_fileandg_recaresize_t. So the multiply truncates. The default file size is 8 GB (diskbench.c:268), so this is the default path, not a corner.Evidence
Same path, same arguments, the two runs back to back, temp file deleted between them.
The sequential rows barely move, which is what makes the pair comparable — only the random path changed.
Two failure modes, and the quiet one is worse
Loud: offsets that wrap negative are refused by
waste_pread'soff < 0guard, which returns-1without touching errno — henceshort read -1: No error. The loop thenbreaks, so that thread stops early while the bytes it never read still divide into its elapsed time. That is why the before column inverts with thread count: more threads means some thread truncates sooner.Quiet: offsets that wrap positive do not fail at all. They read the wrong place, successfully. A run keeps its entire working set inside the first 2 GiB while reporting the size it was asked for — and a 2 GiB hot region sits inside a consumer SSD's SLC cache and DRAM, which is exactly the flattery this tool exists to avoid. That is the reading I would worry about: not the runs that errored, but any run that didn't.
The garbage is also not reproducible. An earlier run of the same unmodified binary gave
0.34 / 0.80 / 1.02rather than0.36 / 0.11 / 0.06. At 2 GB, where nothing truncates, the same binary already reports a clean monotonic1.58 / 2.00 / 2.33.This matters beyond the tool:
docs/GATES.mdGate H sized the whole storage premise on this program, andREADME.mdquotes 12.78 GB/s internal vs 0.94 GB/s USB from it. Those were measured on macOS, whereoff_tis 64-bit, so they are unaffected — but any x86/Windows contributor reproducing Gate H would have been measuring a 2 GiB working set.The change
One line plus a comment explaining why it must not drift back:
Builds clean under
-Wall -Wextra. No other file touched.Noted, not fixed
diskbench.c:277hasg_rec = (size_t)(rec_mb * (1u << 20)) & ~4095UL;. Under LLP64~4095ULis a 32-bit mask that zero-extends, so it clears the high 32 bits of asize_t. Harmless today — it only bites a record size ≥ 4 GiB — but it is the same class of bug, and~(size_t)4095would close it. Left out to keep this diff to the defect I actually measured; say the word and I will fold it in.Caveat on the absolute numbers
Throughput here is depressed by an unrelated model conversion running on the same drive. That load is common to both columns and is visible in the sequential rows. These are a before/after pair, not a device rating for the SN7100 — I will send a clean number for that separately.
Context
Found while standing up a native Windows x86 measurement box (Zen 2, 128 GB, PCIe 4.0 NVMe) to work on Gate 7. Companion to #42, which fixes the suite reporting missing tools as engine failures on the same platform.
🤖 Generated with Claude Code
https://claude.ai/code/session_01B5eVMiauR4Lkt8Dhc67MNb