Skip to content

docs(performance): republish the comparative table at 3.2.0 - #415

Merged
lesnik512 merged 1 commit into
mainfrom
docs/performance-3.2.0
Aug 3, 2026
Merged

docs(performance): republish the comparative table at 3.2.0#415
lesnik512 merged 1 commit into
mainfrom
docs/performance-3.2.0

Conversation

@lesnik512

Copy link
Copy Markdown
Member

Why

docs/introduction/performance.md reported modern-di 3.1.2 and predated all four of 3.2.0's resolve-path trims. It is the page carrying our comparative claims against dependency-injector, dishka, that-depends and wireup, and it was one published version behind what pip install modern-di now gives you.

Design

Tables regenerated with just bench-report (5 paired runs) and published verbatim -- never hand-assembled. I ran it a second time, independently, before writing any prose: the two passes agree closely (C1 347/353 ns, C2 157/157, C3 957/965; every ratio within 0.05), so the run published here is a reproducible measurement rather than one sample.

The finding that shaped the rewrite: every absolute on the page is 23-29% higher than the 2026-07-28 publication -- for all five frameworks, on the same machine, same macOS 26.5, same CPython 3.14.6, same pinned rival versions. The implied rival absolutes this page already prints moved as a block (dependency-injector's C2 hit ~48 -> ~60 ns, that-depends ~67 -> ~83, dishka ~172 -> ~215, wireup ~76 -> ~95). The machine was slower on the day. Rather than publish a 25% worse absolute column and let readers conclude modern-di regressed, the page now states the shift outright and reads the ratios against it -- which is what the paired-per-run methodology exists for.

Against that baseline, the cells that moved are exactly the paths 3.2.0 touched:

3.1.2 3.2.0
C2 vs dependency-injector 2.94 2.63
C2 vs that-depends 2.13 1.90
C2 by-type vs dishka 1.06 0.98
C2 by-type vs wireup 2.40 2.22
C6 vs dishka 1.65 1.56
C6 vs wireup 1.38 1.27

C1/C3/C4 are the control and behave like one: twelve ratio cells, max move 0.04, four up / three down / five unchanged. C2's four cells moved 0.08-0.31 and C6's 0.03-0.11, every one toward modern-di.

The arithmetic is stated so it can be checked: modern-di's C2 rose 10.6% against a pack that rose 24-25%; unchanged it would sit near 176 ns, and 3.2.0's MAKE_CELL removal measures -11.3%, giving 156 against the 157 published.

modern-di's warm singleton is now faster than dishka's on dishka's own by-type basis (0.98) -- the first publication where that cell is below 1.0. I have written it as "level, marginally ahead" rather than a decisive lead, because the spread comes close to covering 1.00.

Also re-measured on the same machine: the asyncio floor inside every C4 cell, 0.31 -> 0.35 us per request, since that figure is machine-dependent and would otherwise have gone stale silently.

Non-goals

  • No new alias scenario. 3.2.0's alias work (-22%) has no cell here, and the page says so rather than leaving a reader to assume the tables cover everything. Adding a C7 would change the published scenario set, which is a separate decision.
  • No re-run on a quieter machine to recover the old absolutes. The shift is real and reproducible; disclosing it is more useful than hiding it.

Verification

just lint-ci: clean, including the Markdown link/anchor checker. just check-links: OK.

just docs-build could not run locally -- it fetches mkdocs from PyPI and this environment has no network for it. CI runs it on this PR, which is the check that matters for the nav and link warnings.

Every ratio, absolute and derived figure in the prose was recomputed from the published run; I grepped for surviving 3.1.2-era numbers and the only remaining ones are deliberate historical comparisons and the version-history section.


Before merging

  • Behaviour changed? No code in this PR.
  • Rejected an alternative? None that would be re-litigated.
  • Found real work you are not doing now? The missing alias scenario in the comparative suite, stated under Non-goals and in the page itself.
  • just lint-ci passes; just test-ci unaffected (docs only).

Regenerated with just bench-report (5 paired runs, reproduced by a second
independent 5-run pass). Every absolute is 23-29% above the 2026-07-28
publication for all five frameworks on identical library, rival, CPython and OS
versions -- the machine was slower on the day -- so the page now states that
outright and reads the ratios against it.

Against that shift, C2 and C6 moved toward modern-di and C1/C3/C4 did not, which
matches exactly what 3.2.0 touched. modern-di's warm singleton is now faster
than dishka's on dishka's own by-type basis (0.98), the first time that cell has
gone below 1.0. The event-loop floor inside every C4 cell was re-measured on the
same machine (0.31 -> 0.35 us per request).

The alias hop has no cell here; the suite has no alias scenario.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@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: a783d34 Previous: bb8794c Ratio
benchmarks/test_guard_by_type.py::test_g16_resolve_by_type 2164401.082438699 iter/sec (stddev: 2.3648389538978368e-7) 2019181.2353840743 iter/sec (stddev: 2.666764939640821e-7) 0.93
benchmarks/test_guard_by_type.py::test_g17_resolve_by_type_large_registry 2671665.888731955 iter/sec (stddev: 5.442083947869368e-8) 2573115.8072158024 iter/sec (stddev: 6.926923363934469e-8) 0.96
benchmarks/test_guard_cold.py::test_g8_cold_first_resolve 23877.899821643336 iter/sec (stddev: 0.000020834385935854022) 24097.39715231142 iter/sec (stddev: 0.00001787777072004501) 1.01
benchmarks/test_guard_concurrency.py::test_g14_concurrent_cached_hit[1] 427.92736293575393 iter/sec (stddev: 0.00003489070252223252) 431.67108978858033 iter/sec (stddev: 0.00008907895116799332) 1.01
benchmarks/test_guard_concurrency.py::test_g14_concurrent_cached_hit[2] 404.9740240554243 iter/sec (stddev: 0.00006366351467864004) 396.46131091162596 iter/sec (stddev: 0.000047395030597096453) 0.98
benchmarks/test_guard_concurrency.py::test_g14_concurrent_cached_hit[4] 359.3159382815722 iter/sec (stddev: 0.00015467660006469294) 351.15399216035996 iter/sec (stddev: 0.0001675935139304426) 0.98
benchmarks/test_guard_concurrency.py::test_g15_concurrent_first_resolve[1] 2294.948546679101 iter/sec (stddev: 0.000028650654652852542) 1915.1996822300337 iter/sec (stddev: 0.00009439567009852235) 0.83
benchmarks/test_guard_concurrency.py::test_g15_concurrent_first_resolve[2] 1588.730204604128 iter/sec (stddev: 0.00034945455977955525) 1475.0720566551136 iter/sec (stddev: 0.0003267425748683087) 0.93
benchmarks/test_guard_concurrency.py::test_g15_concurrent_first_resolve[4] 1193.6097594942505 iter/sec (stddev: 0.00003609625055284982) 1086.933896973317 iter/sec (stddev: 0.00004751332275126381) 0.91
benchmarks/test_guard_lifecycle.py::test_g6_build_child_container 668360.0795311286 iter/sec (stddev: 4.808512275897948e-7) 681375.2366989609 iter/sec (stddev: 0.0000011975191328828107) 1.02
benchmarks/test_guard_lifecycle.py::test_g6b_build_child_container_auto_scope 629105.7951969058 iter/sec (stddev: 4.93666191215643e-7) 648770.3052589889 iter/sec (stddev: 4.6421942393821645e-7) 1.03
benchmarks/test_guard_lifecycle.py::test_g7_request_lifecycle_batch 2402.799082861268 iter/sec (stddev: 0.00001918674188455211) 2322.6008299153827 iter/sec (stddev: 0.00003192617391221412) 0.97
benchmarks/test_guard_lifecycle.py::test_g7c_event_loop_floor_control 62181.66777729213 iter/sec (stddev: 0.0000020346677352899183) 62612.44186400999 iter/sec (stddev: 0.0000019367844975850882) 1.01
benchmarks/test_guard_lifecycle.py::test_g13_teardown_at_scale 46597.43742679612 iter/sec (stddev: 0.0000020693079263258163) 46373.322180892625 iter/sec (stddev: 0.0000021399737339881666) 1.00
benchmarks/test_guard_resolve.py::test_g1_transient_resolve 1346128.8054790264 iter/sec (stddev: 7.377308180034315e-7) 1330558.9044018583 iter/sec (stddev: 3.5143313542242677e-7) 0.99
benchmarks/test_guard_resolve.py::test_g2_cached_resolve 3420318.795433564 iter/sec (stddev: 5.062500962017588e-8) 3289436.386684446 iter/sec (stddev: 4.8116935656836214e-8) 0.96
benchmarks/test_guard_resolve.py::test_g3_deep_chain 522401.88670970744 iter/sec (stddev: 4.6938294849708725e-7) 516234.312285702 iter/sec (stddev: 5.562755524293426e-7) 0.99
benchmarks/test_guard_resolve.py::test_g4_wide_resolve 324940.7494024595 iter/sec (stddev: 5.88039607071334e-7) 324763.134943507 iter/sec (stddev: 6.578727884301413e-7) 1.00
benchmarks/test_guard_resolve.py::test_g5_cross_scope 1163091.192513428 iter/sec (stddev: 3.561650722500084e-7) 1131250.1665050173 iter/sec (stddev: 3.659748695694099e-7) 0.97
benchmarks/test_guard_resolve.py::test_g9_context_resolve 645865.8156131203 iter/sec (stddev: 4.811536505466293e-7) 650099.8557224047 iter/sec (stddev: 5.042087123500081e-7) 1.01
benchmarks/test_guard_resolve.py::test_g12_override_active_resolve 392731.3919028174 iter/sec (stddev: 4.681645734967068e-7) 391370.2035443222 iter/sec (stddev: 4.3516683943055756e-7) 1.00
benchmarks/test_guard_validate.py::test_g10_validate_deep_chain 28370.930127348573 iter/sec (stddev: 0.0000035748254524779354) 27353.337807909375 iter/sec (stddev: 0.0000045394108062455774) 0.96
benchmarks/test_guard_validate.py::test_g11_validate_wide 17018.9170255009 iter/sec (stddev: 0.000005312913804031902) 16743.572312827022 iter/sec (stddev: 0.000004839805353893621) 0.98

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

@lesnik512
lesnik512 merged commit 10e4197 into main Aug 3, 2026
9 checks passed
@lesnik512
lesnik512 deleted the docs/performance-3.2.0 branch August 3, 2026 10:44
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