Skip to content

CallSiteValidator doesn't cache results of the walk #96253

@christiaanderidder

Description

@christiaanderidder

Description

DI scope validation does not cache previously walked trees, leading to performance degradation
When running scope validation on build for a project with around ~4500 dependencies in the DI-container, it takes more than a minute to build the container on my machine (MacBook Pro 2023 M2 Max).
This issue seems to have been reported in the past by @davidfowl in dotnet/extensions#2353 and a solution was partially implemented in dotnet/extensions#2374.
However, both seem to have gotten lost when Microsoft.Extensions.DependencyInjection was moved to this repository.

Configuration

Together with @siematypie, I recreated the benchmark used in the original PR for https://github.com/dotnet/performance to verify the issue and implemented a fix based on the work done by @brandondahler.

The proposed fix can be found here: #96254

The benchmark results:

BenchmarkDotNet v0.13.11-nightly.20231126.107, macOS Sonoma 14.3 (23D5033f) [Darwin 23.3.0]
Apple M2 Max, 1 CPU, 12 logical and 12 physical cores
.NET SDK 9.0.100-alpha.1.23620.7
  [Host]     : .NET 9.0.0 (9.0.23.61907), Arm64 RyuJIT AdvSIMD
  Job-AZASTU : .NET 9.0.0 (42.42.42.42424), Arm64 RyuJIT AdvSIMD
  Job-MAUZGI : .NET 9.0.0 (42.42.42.42424), Arm64 RyuJIT AdvSIMD

PowerPlanMode=00000000-0000-0000-0000-000000000000  IterationTime=250.0000 ms  MaxIterationCount=20  
MinIterationCount=15  WarmupCount=1  

Method Job Toolchain Mean Error StdDev Median Min Max Ratio Gen0 Gen1 Allocated Alloc Ratio
ValidateCallSite Job-AZASTU /runtime-baseline/artifacts/bin/testhost/net9.0-osx-Debug-arm64/shared/Microsoft.NETCore.App/9.0.0/corerun 86.55 μs 0.415 μs 0.347 μs 86.42 μs 86.22 μs 87.40 μs 1.00 2.7322 0.3415 23.41 KB 1.00
ValidateCallSite Job-MAUZGI /runtime/artifacts/bin/testhost/net9.0-osx-Debug-arm64/shared/Microsoft.NETCore.App/9.0.0/corerun 32.97 μs 0.074 μs 0.069 μs 32.95 μs 32.90 μs 33.10 μs 0.38 3.6842 0.7895 30.64 KB 1.31

The benchmark used:

using System;
using BenchmarkDotNet.Attributes;
using MicroBenchmarks;

namespace Microsoft.Extensions.DependencyInjection;

[BenchmarkCategory(Categories.Libraries)]
public class CallSiteValidator
{
    private ServiceCollection _services;

    [GlobalSetup]
    public void Setup()
    {
        _services = new ServiceCollection();
        _services.AddTransient<A>();
        _services.AddTransient<B>();
        _services.AddTransient<C>();
        _services.AddTransient<D>();
        _services.AddTransient<E>();
        _services.AddTransient<F>();
        _services.AddTransient<G>();
        _services.AddTransient<H>();
        _services.AddTransient<I>();
        _services.AddTransient<J>();
        _services.AddTransient<K>();
        _services.AddTransient<L>();
        _services.AddTransient<M>();
        _services.AddTransient<N>();
        _services.AddTransient<O>();
        _services.AddTransient<P>();
    }

    [Benchmark]
    public void ValidateCallSite()
    {
        _services.BuildServiceProvider(new ServiceProviderOptions { ValidateScopes = true, ValidateOnBuild = true });
    }

    private class A
    {
        public A(B b, C c, D d, E e, F f, G g, H h, I i, J j, K k, L l)
        {
        }
    }

    private class B
    {
        public B(C c, D d, E e, F f, G g, H h, I i, J j, K k, L l)
        {
        }
    }

    private class C
    {
        public C(D d, E e, F f, G g, H h, I i, J j, K k, L l)
        {
        }
    }

    private class D
    {
        public D(E e, F f, G g, H h, I i, J j, K k, L l)
        {

        }
    }

    private class E
    {
        public E(F f, G g, H h, I i, J j, K k, L l)
        {
        }
    }

    private class F
    {
        public F(G g, H h, I i, J j, K k, L l)
        {
        }
    }

    private class G
    {
        public G(H h, I i, J j, K k, L l)
        {
        }
    }

    private class H
    {
        public H(I i, J j, K k, L l)
        {
        }
    }

    private class I
    {
        public I(J j, K k, L l)
        {
        }
    }

    private class J
    {
        public J(K k, L l)
        {
        }
    }

    private class K
    {
        public K(L l)
        {
        }
    }

    private class L
    {
        public L(M m)
        {
        }
    }

    private class M
    {
        public M(N n)
        {
        }
    }

    private class N
    {
        public N(O o)
        {
        }
    }

    private class O
    {
        public O(P p)
        {
        }
    }

    private class P
    {
        public P()
        {
        }
    }

}

On the same machine, I also tried the dependency graph from https://github.com/sergey-kolodiy/AspNetCore.DI/blob/master/AspNetCore.DI/AspNetCore.DI.Default/Startup.cs mentioned in the original issue.

Without the fix applied the benchmark was cancelled during the warmup phase due to the long runtime

OverheadJitting  1: 1 op, 97042.00 ns, 97.0420 us/op
WorkloadJitting  1: 1 op, 414446043542.00 ns, 414.4460 s/op

WorkloadWarmup   1: 1 op, 415264853000.00 ns, 415.2649 s/op 

With the fix applied the benchmark completed:

BenchmarkDotNet v0.13.11-nightly.20231126.107, macOS Sonoma 14.3 (23D5033f) [Darwin 23.3.0]
Apple M2 Max, 1 CPU, 12 logical and 12 physical cores
.NET SDK 9.0.100-alpha.1.23620.7
  [Host]     : .NET 9.0.0 (9.0.23.61907), Arm64 RyuJIT AdvSIMD
  Job-WOBNXZ : .NET 9.0.0 (42.42.42.42424), Arm64 RyuJIT AdvSIMD

PowerPlanMode=00000000-0000-0000-0000-000000000000  Toolchain=CoreRun  IterationTime=250.0000 ms  
MaxIterationCount=20  MinIterationCount=15  WarmupCount=1  
Method Mean Error StdDev Median Min Max Gen0 Gen1 Allocated
ValidateCallSite 180.3 μs 0.64 μs 0.56 μs 180.3 μs 179.3 μs 181.5 μs 14.2045 1.4205 119.28 KB

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions