Conversation
There was a problem hiding this comment.
Benchmark
Details
| Benchmark suite | Current: 686a29e | Previous: ed03e40 | Ratio |
|---|---|---|---|
benchmarks/test_guard_by_type.py::test_g16_resolve_by_type |
7054698.605661299 iter/sec (stddev: 7.213454438084297e-9) |
3932685.1717558345 iter/sec (stddev: 1.0184531548305192e-8) |
0.56 |
benchmarks/test_guard_by_type.py::test_g17_resolve_by_type_large_registry |
6923417.448730081 iter/sec (stddev: 7.1530960902748884e-9) |
4786498.703950583 iter/sec (stddev: 1.0412697941283034e-8) |
0.69 |
benchmarks/test_guard_cold.py::test_g8_cold_first_resolve |
39178.70858377368 iter/sec (stddev: 0.00003921208273706643) |
28985.44641661876 iter/sec (stddev: 0.0000453175564873903) |
0.74 |
benchmarks/test_guard_cold.py::test_g8b_cold_first_resolve_cached |
30989.22174732888 iter/sec (stddev: 0.00010987501939553956) |
21029.14082419947 iter/sec (stddev: 0.00016926120529396892) |
0.68 |
benchmarks/test_guard_concurrency.py::test_g14_concurrent_cached_hit[1] |
872.4664941281187 iter/sec (stddev: 0.000027813889414004962) |
531.962246153924 iter/sec (stddev: 0.00013413737354511183) |
0.61 |
benchmarks/test_guard_concurrency.py::test_g14_concurrent_cached_hit[2] |
754.4004161061914 iter/sec (stddev: 0.0001892115976950648) |
499.77362753546674 iter/sec (stddev: 0.0002147016661294616) |
0.66 |
benchmarks/test_guard_concurrency.py::test_g14_concurrent_cached_hit[4] |
696.8248070084479 iter/sec (stddev: 0.0002980283495634352) |
457.0261637631503 iter/sec (stddev: 0.0002509693314331106) |
0.66 |
benchmarks/test_guard_concurrency.py::test_g15_concurrent_first_resolve[1] |
2740.1889191312657 iter/sec (stddev: 0.00022596012273320466) |
2316.8389204233918 iter/sec (stddev: 0.0001557263350180912) |
0.85 |
benchmarks/test_guard_concurrency.py::test_g15_concurrent_first_resolve[2] |
2723.9148831102916 iter/sec (stddev: 0.00012714994930978303) |
2049.202793515347 iter/sec (stddev: 0.00014613918907789515) |
0.75 |
benchmarks/test_guard_concurrency.py::test_g15_concurrent_first_resolve[4] |
1912.482657965369 iter/sec (stddev: 0.00013648446723592687) |
1316.371910518061 iter/sec (stddev: 0.00028483705830564907) |
0.69 |
benchmarks/test_guard_concurrency.py::test_g15b_concurrent_first_resolve_sibling_children[1] |
3542.303038957335 iter/sec (stddev: 0.00013613484913974874) |
2474.432054431746 iter/sec (stddev: 0.00015135110164106287) |
0.70 |
benchmarks/test_guard_concurrency.py::test_g15b_concurrent_first_resolve_sibling_children[2] |
1625.933597520766 iter/sec (stddev: 0.00221193789536882) |
1324.597655947015 iter/sec (stddev: 0.0016613915230447756) |
0.81 |
benchmarks/test_guard_concurrency.py::test_g15b_concurrent_first_resolve_sibling_children[4] |
1453.3688951704307 iter/sec (stddev: 0.00020949961130841652) |
910.6558573838981 iter/sec (stddev: 0.00025942806537605225) |
0.63 |
benchmarks/test_guard_lifecycle.py::test_g6_build_child_container |
1817075.1368272228 iter/sec (stddev: 2.616330992070155e-8) |
1127811.0796892864 iter/sec (stddev: 6.557108275066174e-8) |
0.62 |
benchmarks/test_guard_lifecycle.py::test_g6b_build_child_container_auto_scope |
1519743.7316939465 iter/sec (stddev: 6.166647441354265e-8) |
1078313.5747205522 iter/sec (stddev: 4.4463135476439786e-8) |
0.71 |
benchmarks/test_guard_lifecycle.py::test_g7_request_lifecycle_batch |
4765.68343709727 iter/sec (stddev: 0.000006283191163736443) |
2681.4902139929595 iter/sec (stddev: 0.000042732136607835196) |
0.56 |
benchmarks/test_guard_lifecycle.py::test_g7c_event_loop_floor_control |
109902.6520514945 iter/sec (stddev: 9.00315376726702e-7) |
63941.860824470226 iter/sec (stddev: 0.000003045965513923877) |
0.58 |
benchmarks/test_guard_lifecycle.py::test_g13_teardown_at_scale |
78358.09130605997 iter/sec (stddev: 0.000001119604165868517) |
43369.8574924983 iter/sec (stddev: 0.000002413013093489788) |
0.55 |
benchmarks/test_guard_lifecycle.py::test_g13b_teardown_at_scale_async_no_finalizers |
1025.3127767409253 iter/sec (stddev: 0.00007092263331675094) |
627.9987325389316 iter/sec (stddev: 0.0001604562217980534) |
0.61 |
benchmarks/test_guard_resolve.py::test_g1_transient_resolve |
4023424.7814191696 iter/sec (stddev: 2.4795205076209527e-8) |
2688994.463292887 iter/sec (stddev: 2.6881764347600583e-8) |
0.67 |
benchmarks/test_guard_resolve.py::test_g2_cached_resolve |
6807590.136286161 iter/sec (stddev: 8.502370780335337e-9) |
4204452.077497704 iter/sec (stddev: 1.7418687215314142e-8) |
0.62 |
benchmarks/test_guard_resolve.py::test_g3_deep_chain |
1420865.213849033 iter/sec (stddev: 5.813523037519652e-8) |
844328.406516134 iter/sec (stddev: 7.768253348533782e-8) |
0.59 |
benchmarks/test_guard_resolve.py::test_g4_wide_resolve |
834655.6365261488 iter/sec (stddev: 3.7339773939960124e-7) |
506813.6662808018 iter/sec (stddev: 4.860412600362958e-7) |
0.61 |
benchmarks/test_guard_resolve.py::test_g5_cross_scope |
3651274.778672039 iter/sec (stddev: 1.8408555961135055e-8) |
2720897.23219926 iter/sec (stddev: 2.2993349409977034e-8) |
0.75 |
benchmarks/test_guard_resolve.py::test_g9_context_resolve |
2748422.3368610023 iter/sec (stddev: 1.4595624703137366e-7) |
1861513.451392689 iter/sec (stddev: 1.6009051862784957e-7) |
0.68 |
benchmarks/test_guard_resolve.py::test_g12_override_active_resolve |
1401082.0977313295 iter/sec (stddev: 5.345558909402178e-8) |
977159.4833714707 iter/sec (stddev: 4.46171615729783e-8) |
0.70 |
benchmarks/test_guard_resolve.py::test_g18_alias_hop |
6928925.095741165 iter/sec (stddev: 7.904821324776575e-9) |
4921947.994809434 iter/sec (stddev: 1.2287537114567184e-8) |
0.71 |
benchmarks/test_guard_validate.py::test_g10_validate_deep_chain |
51584.953721396916 iter/sec (stddev: 0.000013602537921896537) |
33446.1591201105 iter/sec (stddev: 0.000032851454320323225) |
0.65 |
benchmarks/test_guard_validate.py::test_g11_validate_wide |
30151.70225963155 iter/sec (stddev: 0.000016647612165370002) |
19356.395088714948 iter/sec (stddev: 0.00002563330554109403) |
0.64 |
This comment was automatically generated by workflow using github-action-benchmark.
4 tasks done
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.
Closes #572.
Summary
IntEnummembers of different enums compare and hash by value, so a provider at a customTenancy.TENANT = 2resolved and cached in aScope.SESSIONcontainer. Scopes now match by enum member. Mixing enums in one tree stays supported, as the custom scopes docs show with aMyScope.TENANTchild underScope.APP.Factoryresolvers, theContextProviderresolver and the unwireable-factory resolver comparecontainer.scope is scope._scope_mapstill hashes by value, so a lookup forTenancy.TENANTcan return theScope.SESSIONancestor. The resolver treats a hit whosetarget.scope is not scopeas a miss and calls_navigate, which raises the same scope error a missing scope raises, with the provider's resolution step prepended.find_containerapplies the same identity rules._stamp_group_scopecompares group default scopes by identity. Before this, two groups giving a shared providerScope.SESSIONandTenancy.TENANTraised noGroupScopeConflictError, and a registeredScope.APPprovider could be restamped to another enum's1member withoutProviderScopeFrozenError.validate()reports a newScopeEnumMismatchError(aRegistrationError) for an edge whose dependency's effective scope has the same value as the parent's but is a different member. Such an edge can never resolve: a chain holds one container per value, since each child's value is higher than its parent's. The error names the provider, the parameter and both members with their enum (Scope.SESSIONandTenancy.TENANT), and draws the chain likeInvalidScopeDependencyError. It has a troubleshooting page and appears in the errors page and nav.docs/migration/to-4.x.md, covering resolution,GroupScopeConflictErrorandProviderScopeFrozenError, and a paragraph in the custom scopes section ofdocs/providers/scopes.md.Design decisions
ison a hit. The same-scope path swaps==foris, which costs nothing extra. Frame counts are unchanged, and the ADR-0001 frame budget tests pass as they are.find_container's existing branches. ResolvingTenancy.TENANTfrom aSESSIONorREQUESTcontainer raisesScopeSkippedError, and resolving it fromAPPraisesScopeNotInitializedError. Resolution needs no new error class.validate()flags only what can never resolve. A dependency on a shallower scope from another enum can resolve: aScope.REQUESTprovider that depends on aConflictingScope.LOWER_THAN_REQUEST(value 2) provider works in anAPP → LOWER_THAN_REQUEST → REQUESTchain, so it is not reported. A test covers that case. A dependency on a deeper scope from another enum is stillInvalidScopeDependencyError.Container.__init__andbuild_child_container(value ordering, as documented),next_deeper(already keyed by(type, member)), cache placement (per container, so correct once navigation is), and_scope_mapconstruction. The map cannot hold two members with one value because a chain strictly increases by value.Timings, best of 7 runs of 2,000,000
resolvecalls on a cached factory from aREQUESTcontainer (macOS, CPython fromuv sync):The cross-scope path costs about 5 ns more because of the
target.scope is not scopecheck. Keying_scope_mapbyid(member), with the id baked into the resolver globals, would remove that cost. I kept theischeck because the map then still holds the scope members themselves, which keeps it readable in a debugger and in tests.Test plan
tests/test_custom_scope.py, using the existingConflictingScopeenum: cached and transient factories,ContextProviderand an unwireable factory atConflictingScope.LOWER_THAN_REQUEST(value 2) raiseScopeSkippedErrorfrom aSESSIONcontainer and from aREQUESTchild, and nothing is cached in theSESSIONcontainer.find_containermatches by member. The documentedScope.APPplusMyScope.TENANTtree validates and resolves.validate()tests intests/test_custom_scope.py: aSESSIONfactory that depends on aConflictingScope.LOWER_THAN_REQUESTprovider raisesValidationFailedErrorholdingScopeEnumMismatchError, the shallower cross-enum dependency validates and resolves, and a deeper one staysInvalidScopeDependencyErrorScopeEnumMismatchErrorintests/test_error_rendering.pytests/test_group.pyforGroupScopeConflictErrorandProviderScopeFrozenErrorwith same-valued scopes from different enumsjust lint-cijust test-ci(622 passed, 100% coverage)mkdocs build --strict