Plate 57
memoryview vs bytes Slice: Localhost Lab
Aditya Challa3 min read
Intro — what this post promises
Window into a buffer with memoryview vs bytes slicing. This lab reports ops/s on Linux localhost, plus whether the result is a view or a new bytes object.
Related links:
- shlex vs split localhost lab
- cmath vs math hypot localhost lab
- signal vs event wakeup localhost lab
- path read text vs open localhost lab
- intenum vs int localhost lab
- uuid4 vs uuid1 localhost lab
- platform vs uname localhost lab
- selectors vs select localhost lab
Lab honesty (1 Oct 2026 IST): Python 3.13.5. Affiliates: 0. Buffer 65536 bytes; window 1024 bytes.
Verdict up front (n=200000): bytes slice ~14645126 ops/s; memoryview slice ~14877648; materialize via tobytes() ~9375358 vs wrapping a bytes slice ~6273259.
Arms
| Arm | Pattern |
|---|---|
raw[mid:mid+chunk] | bytes slice |
mv[mid:mid+chunk] | memoryview slice |
bytes(raw[...]) | redundant materialize |
mv[...].tobytes() | explicit copy from view |
| index-sum over window | per-byte access |
Seven rounds, p50. Contents equal (equal=True); mv slice is a view (mv_is_view=True).
Lab topology
Script: lab-evidence/141-memoryview-vs-bytes/results/run_lab.py.
Lead table (p50 ops/s)
| Arm | ops/s |
|---|---|
| memoryview slice | 14877648 |
| bytes slice | 14645126 |
| memoryview.tobytes | 9375358 |
| bytes slice materialize | 6273259 |
| memoryview index-sum | 203593 |
| bytes index-sum | 186724 |
Slice-object creation rates were close; the semantic win is zero-copy views for parsers that only peek.
Reading it for SRE work
- Protocol parsers / checksum windows →
memoryviewso you do not copy every header. - Need a real
bytesto store/hash long-term →.tobytes()once at the boundary. - Microbench ops/s alone understate the win — watch allocations under tracemalloc (lab 131) when slicing large payloads in a loop.
- Do not keep memoryviews into buffers you will mutate/free unsafely.
When tobytes matters
Explicit tobytes() ran at ~9375358 ops/s, faster here than bytes(bytes_slice) double work (~6273259). Prefer: view while parsing; copy once when ownership must detach.
Index walks were similar (~203593 vs ~186724) — pick memoryview for the allocation story, not a huge ALU miracle.
Allocation story
Creating a slice object was cheap for both APIs here (~14877648 vs ~14645126 ops/s). The difference appears when you do it on multi-megabyte payloads thousands of times per request: bytes slices retain full bytes objects; memoryviews share the buffer. Pair this lab with tracemalloc (lab 131) on your real frame sizes before declaring victory.
For socket code, prefer recv_into / writable memoryviews when filling buffers; this post only covers read-only windows into existing bytes.
Pitfalls
- Assuming every bytes slice is free (it still builds a bytes object).
- Exporting a memoryview after the underlying buffer is released.
- Comparing only ops/s without allocation metrics on multi-MB payloads.
- Confusing this with lab 63 bytes vs bytearray mutability.
Reproduce
Evidence: summary.json, summary.txt.
Limits
One Linux box, 64 KiB buffer, 1 KiB windows. Not numpy memmap, not sock.recv_into benches.
Release memoryviews promptly in finally blocks when wrapping memory-mapped files or shared buffers.
Benchmark with your frame size — 1 KiB windows on 64 KiB buffers are illustrative, not universal.
Document buffer lifetime next to each memoryview producer in the runbook.
Takeaway
memoryview slices (~14877648 ops/s) matched bytes-slice create rates (~14645126) while staying a view. Use memoryview for zero-copy parsing; copy with tobytes() only when you need owned bytes.
Lab evidence
What I found running this
Ran the packaged localhost lab on Linux with Python 3.13.5. Seven rounds, p50: memoryview slices reached 14,877,648 ops/s versus bytes at 14,645,126, and the view stayed zero-copy; tobytes() made an owned copy. Affiliates: 0.