4 min read
Build one large string from many chunks: io.StringIO.write, list.append + "".join, and cautionary +=. This lab reports MB/s (and builds/s) on Linux localhost.
It is not the string-concat-vs-join post (lab 49) alone — here the headline is StringIO as a file-like builder versus the classic join pattern (with += as the footgun).
Related links:
Lab honesty (1 Oct 2026 IST): Python 3.13.5. Affiliates: 0. No Docker. Chunk = 32 ASCII chars.
Verdict up front (20 000 chunks ≈ 625 KiB): list+join ~1209.5 MB/s; StringIO ~700.7 MB/s; += ~623.4 MB/s. Prefer join for pure assembly; use StringIO when you need a file-like API (write/getvalue).
| Arm | Pattern |
|---|---|
StringIO.write then getvalue | file-like builder |
list + "".join | classic assembly |
+= in a loop | cautionary |
getvalue every write | anti-pattern (2k only) |
Script: lab-evidence/101-stringio-vs-list-join/results/run_lab.py.
| Arm | MB/s | builds/s |
|---|---|---|
| list + join | 1209.5 | 1981.6 |
| StringIO.write | 700.7 | 1148.0 |
+= loop | 623.4 | 1021.3 |
Join leads. StringIO is respectable when you want stream semantics. += trails even at 20 k chunks.
| Chunks | StringIO MB/s | list+join MB/s |
|---|---|---|
| 2 000 | 832.6 | 1196.3 |
| 20 000 | 700.7 | 1209.5 |
| 100 000 | 645.8 | 1091.5 |
At 100 k chunks (~3.05 MiB), join still leads (~1091.5 vs ~645.8 MB/s).
At 2 k chunks, calling getvalue() every iteration fell to ~28.8 MB/s — roughly 28.9× slower than a single final getvalue. Do not snapshot the buffer inside the hot loop.
write, seek, tell).For “glue N known strings,” "".join(parts) remains the default.
We used a fixed 32-character ASCII chunk so rates stay comparable across arms. Very tiny chunks (1–4 chars) raise call overhead and shrink absolute MB/s for everyone; huge chunks shift the bottleneck toward memory bandwidth. Re-run with your real fragment sizes before locking a design choice.
StringIO is for str. For bytes pipelines use BytesIO / spooling (lab 87). Mixing encode/decode inside a hot builder will dominate any StringIO-vs-join delta measured here.
+= in loops of unknown length.getvalue per chunk.+= because “CPython optimizes it” — still lost here.Evidence: summary.json, summary.txt.
One Linux box. ASCII chunks only. Not Unicode-heavy normalization. Not concurrent writers.
At 20 k × 32-char chunks, list+join ~1209.5 MB/s beat StringIO ~700.7 MB/s and += ~623.4 MB/s. Use join for speed; StringIO for file-like builders; never getvalue each write.
Lab evidence
Lab 1 Oct 2026 IST. Python 3.13.5. 20k×32char: list_join 1209.5 MB/s; StringIO 700.7; += 623.4. 100k: join 1091.5 vs StringIO 645.8. Not lab 49 alone. Affiliates: 0. Evidence: lab-evidence/101-stringio-vs-list-join/.
Generators, lazily: what yield actually does to a function.
Summary12 Oct 20261 min read
Hands-on gc.collect cost empty vs cyclic garbage lab: real collect latency plus reclaim counts, measured on Linux localhost today in this lab for SREs.
Hands-on1 Oct 20264 min read
Released October 2024, now security-only, ending 2029. What that means for your projects.
Summary14 Oct 20263 min read