Plate 68
WeakValueDictionary vs dict Cache: Localhost Lab
WeakValueDictionary vs strong dict cache: insert and lookup throughput, plus GC-driven drop on Linux localhost.
Aditya Challa4 min read
Intro — what this post promises
Can weakref.WeakValueDictionary replace a strong dict as a cache-like map? This lab measures insert and lookup ops/s on Linux localhost, then shows entries vanishing after del + gc.collect() when only weak refs remain.
Related links:
- lru cache hit miss localhost lab
- threading local vs dict localhost lab
- setdefault vs defaultdict localhost lab
- decimal vs float sum localhost lab
- pickle vs json roundtrip localhost lab
- asyncio gather vs taskgroup localhost lab
- counter vs dict tally localhost lab
- csv reader vs split localhost lab
Lab honesty (1 Oct 2026 IST): Python 3.13.5. Values are small Payload objects with __weakref__. Affiliates: 0. No Docker. Not an LRU eviction policy lab (see lru_cache lab).
Verdict up front (n=10 000): strong insert ~6027.1 k/s vs weak ~960.1 k/s (~6.28×); strong lookup ~16.37 M/s vs weak ~7.64 M/s (~2.14×). After dropping all strong refs to values and gc.collect(), weak map went 10000 → 0 (dropped 10000).
Arms
| Arm | Pattern |
|---|---|
strong dict | d[k] = obj / d[k].n |
WeakValueDictionary | same API; values held weakly |
| GC drop | build weak map, del object list, gc.collect(), recount |
Lab topology
Script: lab-evidence/86-weakref-vs-dict-cache/results/run_lab.py.
Lead table — n=10 000 (p50)
| Arm | ops/s |
|---|---|
| strong insert | 6027.1 k |
| weak insert | 960.1 k (~6.28× slower) |
| strong lookup | 16.37 M |
| weak lookup | 7.64 M (~2.14× slower) |
Scale sketch
| n | strong insert k/s | weak insert k/s | strong lookup M/s | weak lookup M/s |
|---|---|---|---|---|
| 1 000 | 6966.7 | 1041.0 | 31.07 | 12.22 |
| 10 000 | 6027.1 | 960.1 | 16.37 | 7.64 |
| 50 000 | 2706.7 | 971.5 | 25.13 | 10.91 |
Insert tax for weak maps stayed roughly ~6× at 10 k; lookup closer to ~2×.
GC drop (the point of weak caches)
Scenario B — only the weak map holds values:
- Insert 10 000 objects into
WeakValueDictionary, keep a list of strong refs. delthe list;gc.collect().len(weak)went 10000 → 0.
If a strong dict still owns the same objects, weak entries stay alive — weakrefs do not override other strong owners. That is why “cache” designs often pair weak value maps with an explicit size/TTL policy, not weakrefs alone.
Reading it
- Strong dict wins raw speed for insert/lookup microbenches.
- WeakValueDictionary buys auto-drop when nothing else references the value — pay ~2–6× on this box.
- Use weak value maps for identity caches / registries where leaking objects is worse than a miss; use
functools.lru_cache/ explicit eviction when you need bounded strong residency.
When weakrefs help
| Goal | Prefer |
|---|---|
| Fast hot map, you own lifetime | strong dict |
| Avoid retaining objects nobody else needs | WeakValueDictionary |
| Bounded memo of pure functions | lru_cache |
| Key-side weak | WeakKeyDictionary (not measured here) |
Pitfalls
- Storing objects that cannot be weakly referenced (some builtins/extension types).
- Expecting weak maps to bound memory while another structure still holds strong refs.
- Looking up after GC without handling
KeyError/ missing keys. - Comparing to LRU without measuring hit-rate economics.
Reproduce
Evidence: summary.json, summary.txt.
Limits
One Linux box. Synthetic Payload with slots+__weakref__. Not multi-process. GC timing can vary under load.
Takeaway
Strong dict was about ~6.28× faster to fill and ~2.14× faster to read at n=10 k. WeakValueDictionary dropped all 10000 entries once strong refs vanished — that auto-cleanup is the product, not the Mops/s. Pick weak maps for leak resistance; pick strong maps (or LRU) for speed and explicit residency.
Lab evidence
What I found running this
Lab 1 Oct 2026 IST. Python 3.13.5. n=10k: strong insert 6027.1k/s vs weak 960.1k/s (~6.28x); lookup 16.37M vs 7.64M (~2.14x). GC: weak 10000->0 after del+gc. Affiliates: 0. Evidence: lab-evidence/86-weakref-vs-dict-cache/.
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 75
uuid.uuid4 vs uuid.uuid1: Localhost Lab
Hands-on uuid.uuid4 vs uuid.uuid1 ID generation lab: real ops/s plus version/node checks, 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