docs(roadmap): add the warm-singleton (C2) path - #383
Merged
Conversation
…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.
There was a problem hiding this comment.
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
resolve_providerwrapperresolver_for()is 56 nsfetch_cache_item()is 43.5 nsCacheItem)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, andfetch_cache_item's guarantee that concurrent first-resolvers share oneCacheItem.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
CacheItemand 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-ciandmkdocs build --strictclean.🤖 Generated with Claude Code