Repository navigation
Add io_whole_file benchmark and an opt-in io group - #498
Open
jayzuccarelli wants to merge 2 commits into
Open
jayzuccarelli wants to merge 2 commits into
jayzuccarelli wants to merge 2 commits into
Conversation
Add an "io" benchmark group, resolved through the "io" tag and excluded from the default group, so I/O benchmarks only run with "-b io". io_whole_file reads and writes a whole file in a single call: open().read()/write() in binary mode plus Path.read_bytes/write_bytes at 4 KiB and 1 MiB, and Path.read_text/write_text at 1 MiB (6 results). Part of python#399.
| for _ in range_it: | ||
| with open(path, "rb") as f: | ||
| f.read() | ||
| path.read_bytes() |
There was a problem hiding this comment.
It doesn't make sense to call f.read and path.read_bytes in the same benchmark. IIUC, @cmaloney asked for separate benchmarks: one for f.read and one for path.read_bytes.
Author
There was a problem hiding this comment.
Good point, mixing them made the numbers hard to attribute. Each result now measures a single API: read_bytes/write_bytes are plain open().read() / open().write(), and read_text/write_text are Path.read_text() / Path.write_text(). I dropped the Path.read_bytes/write_bytes variants since they are thin wrappers over the same open() calls; happy to add them back as separate results if you'd rather have them.
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.
First of three PRs for #399, one benchmark each as suggested there. This one adds the shared piece (an
iogroup that is not part ofdefault) together with the first benchmark that uses it.What it measures
io_whole_filereads and writes a whole file in a single call, which is the pattern gh-120754 optimized:Each result measures a single API:
read_bytes_{4k,1m}:open(path, "rb").read()write_bytes_{4k,1m}:open(path, "wb").write(data)read_text_1m/write_text_1m:Path.read_text()/Path.write_text(), utf-8, ASCII contentPath.read_bytes()/write_bytes()are thin wrappers over the sameopen()calls, so they are not measured separately.6 results. 4 KiB is where per-call overhead and the number of system calls dominate; 1 MiB is where buffer allocation and copying dominate. I had an 8 MiB size too, but it scaled linearly from 1 MiB and sits closer to dirty page throttling, so I dropped it to keep the result count small.
Everything runs in a fresh temp dir with a warm page cache and no fsync, so it measures Python's I/O stack and syscall overhead rather than the disk. Writes always create a new file (unlinked outside the timed region): truncating and rewriting an existing file on ext4 triggers writeback on close (
auto_da_alloc), which made a 16 MiB rewrite take ~490 ms +- 220 ms instead of ~30 ms. The run recordsio.DEFAULT_BUFFER_SIZEin the metadata.Setup allocates and frees one 8 MiB block before anything is measured. On glibc that raises malloc's dynamic mmap/trim thresholds above the 1 MiB working set; without it
read_text_1m(which frees two 1 MiB blocks per call) sat right at the trim threshold and, depending on heap history, flipped between ~430 us and ~1.3 ms on the same interpreter because the heap top was handed back to the kernel and faulted in again every call.Why opt-in
Per the issue, I/O benchmarks shouldn't run by default. The benchmark is tagged
io,[group io]picks it up through the tag, and[group default]excludes it by name (a-iogroup exclusion is silently ignored by the manifest parser, so it has to name the benchmark).pyperformance listis unchanged at 100 benchmarks;-b ioselects it.Sample timings (
pyperformance run -b io_whole_file --fast, one untuned Linux box, ext4 /tmp)How to run
Tests
python -m pyperformance.testspasses (it runs-b all, so the io benchmarks execute there too)pyperformance listunchanged at 100,list -b iois justio_whole_file__version__too if you want that in the same PR, as Add bm_base64 covering common base64 module APIs #447 did.Part of #399.
AI disclosure: I used Claude Code to write the benchmark and docs; I reviewed the code and ran it.