Hands-on pipe vs tmpfile IPC lab: 5.32 vs 1.51 GB/s bulk (fsync 0.57); 64 B RTT p50 3.3 us vs 70 us vs 289 us. Localhost measured, no Docker.
6 min read
Need to move bytes between processes on one box? Two defaults fight for the job: pipes (os.pipe / shell |) and temporary files. Folklore says “files are fine on SSD.” This lab measures both shapes with the same payload sizes.
This is a hands-on lab with measured numbers:
fsync).fsync change the story.Related links:
Lab honesty (30 Sep 2026 IST): Shared Linux lab box (8 cores, kernel 6.12). Python 3.13.5. Pipes via os.pipe + fork; tmpfiles under /workspace (overlay). No Docker. No GPU. Affiliates: 0.
Verdict up front: best pipe bulk was 5.32 GB/s (64 KiB chunks). Same 64 MiB via tmpfile buffered hit 1.51 GB/s (3.5×~~ slower); with fsync 0.57 GB/s (~~9×). RTT for 64 B: pipe p50 3.3 µs (~285k RPS) vs tmpfile 70 µs (~12k) vs tmpfile+fsync 289 µs (~2.5k).
| Medium | Shape in this lab |
|---|---|
| Pipe | Producer forks; parent writes, child reads (throughput) or duplex request/response (RTT) |
| Tmpfile buffered | Write whole object, then read it back (page cache friendly) |
Tmpfile + fsync | Same, but flush + os.fsync after write (durability tax) |
Related links:
Honesty on RTT: the tmpfile arm is a same-process mailbox (open/write/read). That is an optimistic lower bound for “leave a file for the next stage.” The pipe RTT is a true two-process duplex. If anything, real multi-process file polling would be worse for files.
| Medium | Chunk | GB/s | Seconds |
|---|---|---|---|
| Pipe | 4 KiB | 1.88 | 0.036 |
| Pipe | 64 KiB | 5.32 | 0.013 |
| Pipe | 1 MiB | 1.41 | 0.048 |
| Tmpfile buffered | 4 KiB | 0.93 | 0.072 |
| Tmpfile buffered | 64 KiB | 1.51 | 0.044 |
| Tmpfile buffered | 1 MiB | 1.26 | 0.053 |
| Tmpfile + fsync | 4 KiB | 0.44 | 0.153 |
| Tmpfile + fsync | 64 KiB | 0.57 | 0.118 |
| Tmpfile + fsync | 1 MiB | 0.63 | 0.107 |
Pipe peaked at 64 KiB chunks. Tmpfile never caught pipe on this pass; fsync roughly halved to thirded buffered tmpfile GB/s. A 128 MiB confirm pass kept the same ranking (pipe ~3.7 GB/s at 64 KiB vs tmpfile ~1.4 / fsync ~0.54).
| Size | Medium | p50 | mean | RPS |
|---|---|---|---|---|
| 64 B | Pipe duplex | 3.3 µs | 3.5 | ~285k |
| 64 B | Tmpfile | 70.5 µs | 83.6 | ~12.0k |
| 64 B | Tmpfile + fsync | 288.7 µs | 396 | ~2.5k |
| 1 B | Pipe | 5.2 µs | 5.8 | ~172k |
| 1 B | Tmpfile | 70.0 µs | 76.2 | ~13.1k |
| 1 B | Tmpfile + fsync | 285 µs | 384 | ~2.6k |
| 64 KiB | Pipe | 18.2 µs | 20.1 | ~50k |
| 64 KiB | Tmpfile | 185 µs | 202 | ~5.0k |
| 64 KiB | Tmpfile + fsync | 471 µs | 633 | ~1.6k |
For chatty handoffs, pipe’s ~21× p50 advantage over buffered files (and ~87× over fsync) is the operational punchline. Durability is not free.
Pipes lose data if nobody is reading and the buffer fills; they do not survive process restart; they are awkward for random access and for “many consumers later.” Tmpfiles (or real paths) win when you need replay, crash durability, multi-reader, or a simple ops story (ls the artifact). This lab measures speed, not those product constraints.
UDS sits in between for same-host RPC — see the UDS vs TCP lab — while pipes stay the cheapest streaming primitive inside one machine.
Related links:
fsync — buffered GB/s looks fine until durability is required.mmap — different tools; not in this package.fsync tax.Pipes crushed tmpfiles for both bulk and RTT on this box: 5.32 GB/s vs 1.51 GB/s buffered (64 MiB / 64 KiB chunks), and 3.3 µs vs 70 µs p50 for 64 B RTT — 289 µs once fsync entered. Use files when you need persistence or ops visibility; use pipes when you need streaming speed between live processes.
Evidence path on the lab box: lab-evidence/19-pipe-vs-tmpfile/results/. Affiliates: 0.
Lab evidence
Lab 30 Sep 2026 IST. Python 3.13.5. 64 MiB throughput: pipe chunk 64 KiB 5.322 GB/s; tmpfile buffered 1.511 GB/s; tmpfile+fsync 0.569 GB/s. RTT 64 B: pipe duplex p50 3.3 µs ~285k RPS; tmpfile mailbox p50 70.5 µs ~12k; +fsync p50 288.7 µs ~2.5k. Pipe 1 B p50 5.2 µs; 64 KiB p50 18.2 µs. 128 MiB confirm: pipe ~3.74 GB/s vs tmpfile ~1.44 / fsync ~0.54. Affiliates: 0. Evidence: lab-evidence/19-pipe-vs-tmpfile/.
Hands-on itertools.islice vs list slice window lab: real ops/s taking ranges from sequences, measured on Linux localhost in this hands-on lab for SREs.
Lab1 Oct 20264 min read
Hands-on heapq.merge vs sorted(chain) multi-way merge: real records/s on pre-sorted lists, measured on Linux localhost today in this hands-on lab for SREs.
Lab1 Oct 20264 min read
Hands-on mmap MAP_SHARED write+msync vs pwrite region update: real MB/s with durability labels, measured on Linux localhost in this hands-on lab for SREs.
Lab1 Oct 20264 min read