Plate 17
subprocess.run vs Popen: Localhost Lab
A measured Linux localhost lab comparing subprocess.run with Popen and communicate for tiny process spawns and captured stdout.
Aditya Challa4 min read
Intro — what this post promises
Spawn a tiny process many times: subprocess.run vs Popen + communicate. This lab reports launches/s for /bin/true, captured echo, and a short seq stdout capture on Linux localhost.
Related links:
- futures as completed vs wait localhost lab
- threadpoolexecutor vs sequential localhost lab
- asyncio gather vs taskgroup localhost lab
- shelve vs pickle dict localhost lab
- secrets vs urandom localhost lab
- scandir vs listdir localhost lab
- tarfile vs zipfile localhost lab
- textwrap fill vs manual localhost lab
Lab honesty (1 Oct 2026 IST): Python 3.13.5. Affiliates: 0. No Docker. No shell=True.
Verdict up front: Popen+/bin/true ~1745.5 launches/s vs run ~1610.6. With pipes: Popen ~1499.8 vs run(capture_output=True) ~1309.5. Echo capture: run ~1136.3 ≈ Popen ~1177.9.
Arms
| Arm | Pattern |
|---|---|
run(["/bin/true"]) | high-level wait |
Popen + communicate | lower-level |
run(..., capture_output=True) | pipes via run |
Popen with PIPEs | explicit pipes |
| echo / seq | small stdout capture |
Lab topology
Script: lab-evidence/95-subprocess-run-vs-popen/results/run_lab.py.
Lead table (p50 launches/s)
| Arm | launches/s |
|---|---|
Popen /bin/true | 1745.5 |
run /bin/true | 1610.6 |
| Popen true + pipes | 1499.8 |
| run true capture | 1309.5 |
| Popen echo communicate | 1177.9 |
| run echo capture | 1136.3 |
| run seq 1..500 | 1065.0 |
| Popen seq 1..500 | 998.8 |
Reading it
run≈Popen+communicatefor these tiny commands — convenience tax is small (~1.08× at best for bare true).capture_output=True/ PIPEs cost more than a no-pipe spawn (true: ~1610.6 → ~1309.5 launches/s).- Prefer
runfor one-shot commands; usePopenwhen you need streaming, overlap, or custom pipe wiring. - Spawning
/bin/true1000× is still an anti-pattern — batch work inside one process when you can.
Convenience vs control
subprocess.run is the recommended API for most cases. This lab’s numbers support that: you are not leaving meaningful launches/s on the table versus hand-rolled Popen for fire-and-wait. Reach for Popen when run cannot express the lifecycle.
Why seq flipped slightly
On seq 1 500, run edged Popen (~1065.0 vs ~998.8 launches/s). Noise and helper overhead dominate at this scale; treat them as tied. The durable lesson is pipe setup cost, not a permanent ranking.
Capture cost reminder
Opening pipes for a command that writes nothing (/bin/true) still pays pipe setup. Only request capture_output / PIPEs when you will read stdout/stderr. For status-only helpers, bare run/Popen.wait stays leaner (~1611 vs ~1309 launches/s on this box).
Overlap and streaming
run always waits for the child to exit before returning. If you must read partial stdout while the child still runs, or multiplex several children, Popen is the API. This microbench never overlaps children — it measures spawn+wait convenience — so treat streaming designs as a separate profile.
Security baseline
Pass argument lists, not interpolated shell strings. Both run and Popen are safe when shell=False (default) and argv is controlled. Throughput rankings do not change that rule.
Pitfalls
shell=Truewith untrusted strings.- Deadlocks if you use PIPEs without
communicate/ threads. - Ignoring
check=True/ return codes. - Measuring spawn microbenches and concluding “Python is slow” when the kernel exec dominates.
Reproduce
Evidence: summary.json, summary.txt.
Limits
One Linux box. /bin/true, /bin/echo, seq only. Not posix_spawn tuning, not shell pipelines.
Batch when possible
If a loop calls run hundreds of times for tiny helpers, consider one long-lived worker process or an in-process function instead — spawn overhead will dominate any run vs Popen delta.
Takeaway
Popen ~1745.5 /s vs run ~1610.6 /s for /bin/true; capture paths sit lower (~1309.5–1499.8). Default to subprocess.run; use Popen when you need lower-level control — not for a few percent on tiny spawns.
Lab evidence
What I found running this
Lab 1 Oct 2026 IST. Python 3.13.5. /bin/true: Popen 1745.5/s vs run 1610.6/s; capture run 1309.5/s vs Popen pipes 1499.8/s. Echo≈tied. Affiliates: 0. Evidence: lab-evidence/95-subprocess-run-vs-popen/.
Related links
Plate 17
platform vs os.uname Inventory: Localhost Lab
Hands-on platform.platform vs os.uname host inventory lab: real ops/s plus cache notes, measured on Linux localhost today in this hands-on lab for SREs.
1 Oct 2026
Plate 50
signal vs threading.Event Wakeup: Localhost Lab
Hands-on signal SIGUSR1 vs threading.Event wakeup lab: real p50 latency in microseconds, measured on Linux localhost today in this hands-on lab for SREs.
1 Oct 2026
Plate 76
cmath vs math.hypot Magnitudes: Localhost Lab
Hands-on cmath vs math.hypot magnitude ops lab: real ops/s for abs, polar, and phase, measured on Linux localhost today in this hands-on lab for SREs.
1 Oct 2026