Plate 41
shutil.copyfile vs Manual Copy Lab
Hands-on shutil.copyfile vs manual file copy lab: real MB/s versus open/read/write and Path.read_bytes on local disk, measured on Linux localhost (lab).
Aditya Challa4 min read
Intro — what this post promises
Copying file bytes on disk: is shutil.copyfile faster than a manual open + read/write loop (or Path.read_bytes / write_bytes)? This lab measures MB/s on Linux localhost temp files — not another pathlib-vs-os.path join lab (that is already published).
Related links:
- pathlib vs os.path localhost lab
- sendfile vs userspace copy localhost lab
- bytes vs bytearray localhost lab
- str translate vs replace localhost lab
- setdefault vs defaultdict localhost lab
- copy vs deepcopy localhost lab
- json dumps compact vs indent localhost lab
- perf_counter vs time localhost lab
Lab honesty (1 Oct 2026 IST): Python 3.13.5. Same filesystem tempdir. Affiliates: 0. copyfile copies data only; copy2 also copies metadata.
Verdict up front: 1 MiB — copyfile ~1823 MB/s vs manual 64 KiB chunks ~1542 (~1.18×) vs Path read/write ~1557. 8 MiB — copyfile ~1346 vs read-all ~879 (~1.53×); chunked manual stayed close. Prefer shutil.copyfile (or copy2 when you need mtime/mode); avoid slurping large files into RAM.
Arms
| Arm | Pattern |
|---|---|
copyfile | shutil.copyfile(src, dst) |
copy2 | data + metadata |
| manual chunk 64 KiB | read/write loop |
| manual readall | read() then write() |
| Path read/write | dst.write_bytes(src.read_bytes()) |
Sizes: 4 KiB / 64 KiB / 1 MiB / 8 MiB with repeated copies.
Lab topology
Script: lab-evidence/74-shutil-copyfile-vs-manual/results/run_lab.py.
Lead table — throughput (p50 MB/s)
| Arm | 4 KiB | 64 KiB | 1 MiB | 8 MiB |
|---|---|---|---|---|
| copyfile | 61 | 634 | 1823 | 1346 |
| copy2 | 54 | 551 | 1738 | 1931 |
| manual chunk | 72 | 730 | 1542 | 1420 |
| manual readall | 73 | 733 | 1549 | 879 |
| Path r/w | 69 | 722 | 1557 | 1394 |
Small-file copies/s (4 KiB)
| Arm | copies/s |
|---|---|
| manual readall | 18,724 |
| manual chunk | 18,550 |
| Path r/w | 17,637 |
| copyfile | 15,720 |
| copy2 | 13,845 |
Tiny files are dominated by open/unlink overhead — manual can look “faster” in a tight loop.
Reading it
- MiB-class payloads —
copyfileedged chunked manual by ~1.18× here (C helper, fewer Python trips). - Don’t
read()entire large files — at 8 MiB readall trailed copyfile by ~1.53×. copy2costs metadata on small files; on larger buffered copies it can still win on this box (cache/FS noise — treat as “same league”).- Path convenience ≈ manual readall/chunk for mid sizes — fine for scripts, not a speedup.
Why not pathlib join again?
Lab 47 already covered Path vs os.path for join/exists/stat. This lab answers a different question: how you move bytes once you have two paths.
copyfile vs copy2 in one line
Need only bytes? copyfile. Need the destination to look like the source in ls -l timestamps/mode? copy2. Do not invent a third wrapper that stats and chmods by hand unless you are filtering metadata on purpose.
Pitfalls
- Hand-rolling copies “for control” without chunking large files.
- Using
copy2when you only need bytes — extra syscalls on small files. - Benchmarking only 4 KiB — open overhead hides copy algorithm.
- Cross-filesystem / remote mounts — numbers here are local tempdir.
When to pick what
| Need | Prefer |
|---|---|
| Data-only copy | shutil.copyfile |
| Preserve mtime/mode | shutil.copy2 |
| Custom transform while copying | manual chunked loop |
| Tiny script convenience | Path read/write |
Reproduce
Evidence: /workspace/lab-evidence/74-shutil-copyfile-vs-manual/results/.
Closing
Use shutil for whole-file copies; chunk if you must DIY. On this box 1 MiB copyfile hit ~1823 MB/s (~1.18× chunked manual), and at 8 MiB it beat full-file readall by ~1.53×. Reach for pathlib when paths are the story — for bytes, copyfile is the default.
Lab evidence
What I found running this
Lab 1 Oct 2026 IST. Python 3.13.5. Same filesystem tempdir. Measured 4 KiB, 64 KiB, 1 MiB, and 8 MiB repeated copies. At 1 MiB copyfile reached ~1823 MB/s vs manual chunk ~1542; at 8 MiB copyfile ~1346 vs readall ~879. Affiliates: 0. Evidence: lab-evidence/74-shutil-copyfile-vs-manual/results/.
Related links
Plate 18
fnmatch vs re Name Filter: Localhost Lab
Hands-on fnmatch.filter vs re.compile name-list filtering measured on Linux localhost.
Observability & SRE · 30 Sept 2026
Plate 84
tarfile vs zipfile Create+Extract: Localhost Lab
Hands-on tarfile vs zipfile create+extract lab: real MB/s on a mixed small-file fixture (uncompressed tar vs zip), measured on Linux localhost for SREs.
Observability & SRE · 30 Sept 2026
Plate 85
scandir vs listdir vs iterdir: Localhost Lab
Hands-on os.scandir vs listdir vs Path.iterdir lab: real entries/s for names and is_file on a synthetic tree, measured on Linux localhost (lab) for SREs.
Observability & SRE · 30 Sept 2026