Repository navigation
Avoid descriptor-array allocations in collection lifetime queries (#92) - #104
Conversation
Co-authored-by: Codex <codex@openai.com>
|
Implemented solution for #92: The six collection lifetime predicates now use FindLastServiceDescriptor, which walks the IServiceCollection indexer from Count - 1 toward zero. It returns the first descriptor matching keyed status, serviceType.IsAssignableFrom(descriptor.ServiceType), and—on keyed queries—Equals(descriptor.ServiceKey, serviceKey). The caller compares that descriptor's lifetime. This preserves last matching registration wins: an earlier registration with the requested lifetime cannot override a later matching registration with a different lifetime. Keyed and unkeyed registrations remain separate; key matching keeps value equality. The helper reads the live collection on every query and uses no descriptor/result cache. Argument validation and its precedence remain unchanged, including the previous null-collection "source" parameter name. The public GetServiceDescriptors API retains its array-returning filtering/order contract. 15 new contract cases passed against both the original and optimized implementation across net472, net8.0, net9.0 and net10.0. The full suite passes 1,324 tests per target. Production changes are confined to ServiceCollectionExtensions.Registration.cs; the changelog, tests and reproducible allocation probe accompany them. |
|
Measured allocation gains for #92 (median bytes/query with 10,000 matching descriptors):
The optimized public APIs measured 0 B/query in all 32 cases per runtime, covering collection sizes 0/10/1,000/10,000, keyed/unkeyed registrations, all matches, a match only at the end, a match only at the beginning and no match. Array-based controls preserve the old implementation and reproduce the pre-change public API allocations. Every measured result is checked against the expected boolean. Baseline: 4887f25. Candidate: d311a1c. Release builds, Microsoft DI 10.0.0, DOTNET_TieredCompilation=0; 1,000 warm-ups followed by seven samples per case. Each sample uses 100–10,000 iterations according to collection size, with collection/finalizer wait before sampling. GC.GetAllocatedBytesForCurrentThread measures allocations; setup, keys, delegates and sample storage are outside measurement. The reverse scan returns immediately when the last descriptor matches. A match at the beginning or no match still requires a full scan. These are configuration-time manual allocation measurements in a shared environment, with no throughput, timing or net472 allocation claim. Custom key equality can have its own allocation costs. Full table and runnable probe: repro/Issue92.CollectionLifetime/README.md. |
Collection lifetime predicates currently materialize every matching descriptor before inspecting the last registration. Keyed predicates also allocate a second filtered array.
Use one private reverse-search helper for all six Type-based lifetime predicates; generic overloads continue to forward to them. Stop at the last descriptor matching keyed status, assignable service type and value-equal key, then compare its lifetime. Preserve argument validation and the public GetServiceDescriptors array contract.
Warmed allocation probes now report 0 B/query in every measured keyed/unkeyed case on .NET 8, 9 and 10. With 10,000 matching descriptors, unkeyed queries previously allocated 80,216–211,928 B and keyed queries 160,376–372,728 B. First-match/no-match cases still require a full scan. Reproduction and full results are in repro/Issue92.CollectionLifetime/README.md.
Validation: 15 new contract cases pass against both the baseline and candidate on net472/net8.0/net9.0/net10.0. All 1,324 tests pass per target; Release library targets include netstandard2.0. Tests cover mixed lifetimes, interleaved registrations, equal-but-distinct keys, assignable types, missing matches, mutations, null keys, AnyKey, validation precedence and descriptor-array order. Existing net472 test dependency support warnings remain. Staged whitespace check passed.
Closes #92.
Co-authored-by: Codex codex@openai.com