WeakValueDictionary vs strong dict cache: insert and lookup throughput, plus GC-driven drop on Linux localhost.
4 min read
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:
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).
| 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 |
Script: lab-evidence/86-weakref-vs-dict-cache/results/run_lab.py.
| 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) |
| 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×.
Scenario B — only the weak map holds values:
WeakValueDictionary, keep a list of strong refs.del the 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.
functools.lru_cache / explicit eviction when you need bounded strong residency.| 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) |
KeyError / missing keys.Evidence: summary.json, summary.txt.
One Linux box. Synthetic Payload with slots+__weakref__. Not multi-process. GC timing can vary under load.
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
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/.
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.
Summary1 Oct 20264 min read
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.
Summary1 Oct 20263 min read
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.
Summary1 Oct 20263 min read