Skip to content

docs(roadmap): add the warm-singleton (C2) path - #383

Merged
lesnik512 merged 1 commit into
mainfrom
docs/roadmap-warm-singleton
Jul 28, 2026
Merged

docs(roadmap): add the warm-singleton (C2) path#383
lesnik512 merged 1 commit into
mainfrom
docs/roadmap-warm-singleton

Conversation

@lesnik512

Copy link
Copy Markdown
Member

C2 warm singleton is the one published comparative scenario where modern-di is clearly last — ~3.9x dependency-injector and ~2.8x that-depends. This records it as a known, bounded gap rather than leaving it unremarked next to a benchmark page that publishes it.

Profiled rather than estimated. A warm hit costs ~170 ns on an M4 / CPython 3.14.6:

cost
resolve_provider wrapper 68.8 ns — of which resolver_for() is 56 ns
compiled resolver body 100.7 ns — of which fetch_cache_item() is 43.5 ns
ideal (closed-over CacheItem) 16.1 ns
bare attribute read 10.8 ns

So ~100 ns of the 170 is two Python method calls that are pure indirection on the warm path. Both wrap a memoized dict lookup; inlining each one's hit path and calling the method only on a miss preserves what they exist for — resolver_for's cycle-safe compilation thunk, and fetch_cache_item's guarantee that concurrent first-resolvers share one CacheItem.

Deliberately bounded. The entry says this should roughly halve the cell, not close it: dependency-injector's ~47 ns is a C-level slot read on a Cython core, which pure Python does not reach. Overpromising here would be easy and wrong.

One step named as deferred. An APP-scoped resolver could close over its CacheItem and reach the ~16 ns floor, because the target container is invariant per registry. But that invariance holds only because one registry belongs to one root — so the registry would have to hold a reference back to its root, which is the container reference cycle #381 removed in 3.1.1. It needs a weakref and a proof for ~30 ns, so the roadmap names it rather than implying it is free.

Roadmap-only, no bundle — matching #377's treatment of the same kind of change. just lint-ci and mkdocs build --strict clean.

🤖 Generated with Claude Code

…bility

C2 is the one published comparative scenario where modern-di is clearly last.
Profiling the warm hit puts ~100 of its ~170 ns in two Python method calls that
are pure indirection: resolver_for on every top-level resolve and
fetch_cache_item inside the compiled resolver. Records the bounded win honestly
(roughly halving the cell, not closing it - dependency-injector's ~47 ns is a
Cython slot read) and names the closed-over-CacheItem step as deferred, since it
would reintroduce the registry-to-root reference the 3.1.1 cycle fix removed.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark

Details
Benchmark suite Current: 3c3bb2f Previous: 2a0536c Ratio
benchmarks/test_guard_by_type.py::test_g16_resolve_by_type 1915939.8227277314 iter/sec (stddev: 2.548302740735089e-7) 1796487.0280517314 iter/sec (stddev: 2.7763334443455044e-7) 0.94
benchmarks/test_guard_by_type.py::test_g17_resolve_by_type_large_registry 2257837.1507550105 iter/sec (stddev: 6.96289384820675e-8) 2179627.663984104 iter/sec (stddev: 8.016286231569078e-8) 0.97
benchmarks/test_guard_cold.py::test_g8_cold_first_resolve 26458.90962832546 iter/sec (stddev: 0.000017331868520981002) 25255.55825930082 iter/sec (stddev: 0.000018372696654884963) 0.95
benchmarks/test_guard_concurrency.py::test_g14_concurrent_cached_hit[1] 305.1507214175042 iter/sec (stddev: 0.000036450076636294355) 309.3259130848511 iter/sec (stddev: 0.0000512011962474514) 1.01
benchmarks/test_guard_concurrency.py::test_g14_concurrent_cached_hit[2] 297.0406609916016 iter/sec (stddev: 0.000076154680774033) 303.25796374112326 iter/sec (stddev: 0.00003376710469222382) 1.02
benchmarks/test_guard_concurrency.py::test_g14_concurrent_cached_hit[4] 273.378598084458 iter/sec (stddev: 0.0001366181981689115) 263.66364665889915 iter/sec (stddev: 0.00014937381374027648) 0.96
benchmarks/test_guard_concurrency.py::test_g15_concurrent_first_resolve[1] 2459.441503873883 iter/sec (stddev: 0.00003133492079952831) 2183.8796450835885 iter/sec (stddev: 0.00003066852357775386) 0.89
benchmarks/test_guard_concurrency.py::test_g15_concurrent_first_resolve[2] 1697.765437915582 iter/sec (stddev: 0.0003539596657911805) 1579.245326298745 iter/sec (stddev: 0.00032543818688865255) 0.93
benchmarks/test_guard_concurrency.py::test_g15_concurrent_first_resolve[4] 1212.7840047053467 iter/sec (stddev: 0.000036451775949121384) 1069.5123681353473 iter/sec (stddev: 0.00006649331022873706) 0.88
benchmarks/test_guard_lifecycle.py::test_g6_build_child_container 626888.4651222749 iter/sec (stddev: 4.901247534253243e-7) 630358.8716190766 iter/sec (stddev: 5.973790852439333e-7) 1.01
benchmarks/test_guard_lifecycle.py::test_g6b_build_child_container_auto_scope 602140.1575513622 iter/sec (stddev: 4.743450238112142e-7) 585955.5097629513 iter/sec (stddev: 6.500369508143625e-7) 0.97
benchmarks/test_guard_lifecycle.py::test_g7_request_lifecycle_batch 2179.9897880180692 iter/sec (stddev: 0.000037315125624385295) 2145.695471467018 iter/sec (stddev: 0.00002687943538884273) 0.98
benchmarks/test_guard_lifecycle.py::test_g7c_event_loop_floor_control 59769.012121769614 iter/sec (stddev: 0.0000053859161292502135) 61835.88161297413 iter/sec (stddev: 0.000001962380823181652) 1.03
benchmarks/test_guard_lifecycle.py::test_g13_teardown_at_scale 45487.5863861313 iter/sec (stddev: 0.000002211816253720355) 44740.76019541694 iter/sec (stddev: 0.000002282308625671442) 0.98
benchmarks/test_guard_resolve.py::test_g1_transient_resolve 1213149.5657524485 iter/sec (stddev: 4.308141820319956e-7) 1197745.7452320245 iter/sec (stddev: 5.392242454142023e-7) 0.99
benchmarks/test_guard_resolve.py::test_g2_cached_resolve 2717479.7619106206 iter/sec (stddev: 6.05678164806788e-8) 2718328.9290847685 iter/sec (stddev: 5.7722662513957005e-8) 1.00
benchmarks/test_guard_resolve.py::test_g3_deep_chain 474918.35744744656 iter/sec (stddev: 0.0000010632182509676277) 482560.009598 iter/sec (stddev: 5.623822961646249e-7) 1.02
benchmarks/test_guard_resolve.py::test_g4_wide_resolve 308169.0570328147 iter/sec (stddev: 6.385737793021829e-7) 302343.2184342751 iter/sec (stddev: 6.511697691239666e-7) 0.98
benchmarks/test_guard_resolve.py::test_g5_cross_scope 1075751.5584052668 iter/sec (stddev: 4.2249654351822014e-7) 1070201.5248234158 iter/sec (stddev: 3.847193037107147e-7) 0.99
benchmarks/test_guard_resolve.py::test_g9_context_resolve 560797.1020554996 iter/sec (stddev: 4.815045871003165e-7) 568073.1299169565 iter/sec (stddev: 5.083638869093008e-7) 1.01
benchmarks/test_guard_resolve.py::test_g12_override_active_resolve 374586.3119586995 iter/sec (stddev: 5.421008126891591e-7) 380720.64773620665 iter/sec (stddev: 4.5124778069338484e-7) 1.02
benchmarks/test_guard_validate.py::test_g10_validate_deep_chain 29322.04700383377 iter/sec (stddev: 0.000004252502869952561) 29106.085744929478 iter/sec (stddev: 0.000003832970481227797) 0.99
benchmarks/test_guard_validate.py::test_g11_validate_wide 17705.405006882283 iter/sec (stddev: 0.00000471046104798708) 17204.25361864011 iter/sec (stddev: 0.000004889364528288628) 0.97

This comment was automatically generated by workflow using github-action-benchmark.

@lesnik512
lesnik512 merged commit 37a92f1 into main Jul 28, 2026
9 checks passed
@lesnik512
lesnik512 deleted the docs/roadmap-warm-singleton branch July 28, 2026 09:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant