Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NET 8.0 Preview 7 Performance Regression: ActivatorUtilities.CreateInstance #91186

Closed
Jakimar opened this issue Aug 28, 2023 · 9 comments · Fixed by #91290
Closed

NET 8.0 Preview 7 Performance Regression: ActivatorUtilities.CreateInstance #91186

Jakimar opened this issue Aug 28, 2023 · 9 comments · Fixed by #91290

Comments

@Jakimar
Copy link

Jakimar commented Aug 28, 2023

Description

I was checking new behavior for "ActivatorUtilities.CreateInstance" in NET 8.0 and noticed some perfomance regression when invoked for class with not empty construcor, as i can see something changed between "Preview 6" and "Preview 7", it's slighly better in last daily build "rc.2.23426.4" but still much worse than NET7.0 .

public class Tester
{
    private static IServiceProvider _spProvider;

    public Tester()
    {
        var sc = new ServiceCollection();
        sc.AddScoped<A>();
        sc.AddScoped<B>();
        sc.AddScoped<C>();
        _spProvider = sc.BuildServiceProvider();
    }
    
    public A? CreateA()
    {
        var scope = _spProvider.CreateScope();
        return ActivatorUtilities.CreateInstance<A>(scope.ServiceProvider);
    }
    
    public B? CreateB()
    {
        var scope = _spProvider.CreateScope();
        return ActivatorUtilities.CreateInstance<B>(scope.ServiceProvider);
    }
    
    public C? CreateC()
    {
        var scope = _spProvider.CreateScope();
        return ActivatorUtilities.CreateInstance<C>(scope.ServiceProvider);
    }
}

    public class A
    { }

    public class B
    {
        private readonly A _a;

        public B(A a)
        {
            _a = a;
        }
    }

    public class C
    {
        private readonly B _b;
        private readonly B _b1;
        private readonly B _b2;
        private readonly B _b3;
        private readonly B _b4;
        private readonly B _b5;
        
        public C(B b, B b1, B b2, B b3, B b4, B b5)
        {
            _b = b;
            _b1 = b1;
            _b2 = b2;
            _b3 = b3;
            _b4 = b4;
            _b5 = b5;
        }
    }

Configuration

BenchmarkDotNet v0.13.7, Windows 10 (10.0.19044.3086/21H2/November2021Update)
11th Gen Intel Core i5-11600K 3.90GHz, 1 CPU, 12 logical and 6 physical cores
.NET SDK 8.0.100-rc.2.23425.18
  [Host]                  : .NET 7.0.1 (7.0.122.56804), X64 RyuJIT AVX2
  7.0.0                   : .NET 7.0.1 (7.0.122.56804), X64 RyuJIT AVX2
  8.0.0-preview.6.23329.7 : .NET 8.0.0 (8.0.23.42311), X64 RyuJIT AVX2
  8.0.0-preview.7.23375.6 : .NET 8.0.0 (8.0.23.42311), X64 RyuJIT AVX2
  8.0.0-rc.2.23426.4      : .NET 8.0.0 (8.0.23.42311), X64 RyuJIT AVX2

Regression?

Data

Method Runtime NuGetReferences Mean Error StdDev Ratio
CreateA .NET 7.0 Microsoft.Extensions.DependencyInjection 7.0.0 122.1 ns 2.05 ns 1.92 ns baseline
CreateA .NET 8.0 Microsoft.Extensions.DependencyInjection 8.0.0-preview.6.23329.7 116.8 ns 2.33 ns 2.50 ns -4%
CreateA .NET 8.0 Microsoft.Extensions.DependencyInjection 8.0.0-preview.7.23375.6 113.6 ns 1.54 ns 1.44 ns -7%
CreateA .NET 8.0 Microsoft.Extensions.DependencyInjection 8.0.0-rc.2.23426.4 119.0 ns 2.42 ns 2.69 ns -3%
CreateB .NET 7.0 Microsoft.Extensions.DependencyInjection 7.0.0 220.3 ns 3.31 ns 3.10 ns baseline
CreateB .NET 8.0 Microsoft.Extensions.DependencyInjection 8.0.0-preview.6.23329.7 218.9 ns 3.83 ns 3.39 ns -1%
CreateB .NET 8.0 Microsoft.Extensions.DependencyInjection 8.0.0-preview.7.23375.6 480.0 ns 3.31 ns 2.76 ns +118%
CreateB .NET 8.0 Microsoft.Extensions.DependencyInjection 8.0.0-rc.2.23426.4 402.6 ns 4.53 ns 4.24 ns +83%
CreateC .NET 7.0 Microsoft.Extensions.DependencyInjection 7.0.0 522.9 ns 4.21 ns 3.73 ns baseline
CreateC .NET 8.0 Microsoft.Extensions.DependencyInjection 8.0.0-preview.6.23329.7 494.1 ns 3.49 ns 2.91 ns -5%
CreateC .NET 8.0 Microsoft.Extensions.DependencyInjection 8.0.0-preview.7.23375.6 2,489.0 ns 26.57 ns 24.85 ns +376%
CreateC .NET 8.0 Microsoft.Extensions.DependencyInjection 8.0.0-rc.2.23426.4 1,604.6 ns 26.34 ns 23.35 ns +207%
@Jakimar Jakimar added the tenet-performance Performance related issue label Aug 28, 2023
@ghost ghost added the untriaged New issue has not been triaged by the area owner label Aug 28, 2023
@dotnet-issue-labeler dotnet-issue-labeler bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Aug 28, 2023
@stephentoub
Copy link
Member

cc: @steveharter

@ghost
Copy link

ghost commented Aug 28, 2023

Tagging subscribers to this area: @dotnet/area-extensions-dependencyinjection
See info in area-owners.md if you want to be subscribed.

Issue Details

Description

I was checking new behavior for "ActivatorUtilities.CreateInstance" in NET 8.0 and noticed some perfomance regression when invoked for class with not empty construcor, as i can see something changed between "Preview 6" and "Preview 7", it's slighly better in last daily build "rc.2.23426.4" but still much worse than NET7.0 .

public class Tester
{
    private static IServiceProvider _spProvider;

    public Tester()
    {
        var sc = new ServiceCollection();
        sc.AddScoped<A>();
        sc.AddScoped<B>();
        sc.AddScoped<C>();
        _spProvider = sc.BuildServiceProvider();
    }
    
    public A? CreateA()
    {
        var scope = _spProvider.CreateScope();
        return ActivatorUtilities.CreateInstance<A>(scope.ServiceProvider);
    }
    
    public B? CreateB()
    {
        var scope = _spProvider.CreateScope();
        return ActivatorUtilities.CreateInstance<B>(scope.ServiceProvider);
    }
    
    public C? CreateC()
    {
        var scope = _spProvider.CreateScope();
        return ActivatorUtilities.CreateInstance<C>(scope.ServiceProvider);
    }
}

    public class A
    { }

    public class B
    {
        private readonly A _a;

        public B(A a)
        {
            _a = a;
        }
    }

    public class C
    {
        private readonly B _b;
        private readonly B _b1;
        private readonly B _b2;
        private readonly B _b3;
        private readonly B _b4;
        private readonly B _b5;
        
        public C(B b, B b1, B b2, B b3, B b4, B b5)
        {
            _b = b;
            _b1 = b1;
            _b2 = b2;
            _b3 = b3;
            _b4 = b4;
            _b5 = b5;
        }
    }

Configuration

BenchmarkDotNet v0.13.7, Windows 10 (10.0.19044.3086/21H2/November2021Update)
11th Gen Intel Core i5-11600K 3.90GHz, 1 CPU, 12 logical and 6 physical cores
.NET SDK 8.0.100-rc.2.23425.18
  [Host]                  : .NET 7.0.1 (7.0.122.56804), X64 RyuJIT AVX2
  7.0.0                   : .NET 7.0.1 (7.0.122.56804), X64 RyuJIT AVX2
  8.0.0-preview.6.23329.7 : .NET 8.0.0 (8.0.23.42311), X64 RyuJIT AVX2
  8.0.0-preview.7.23375.6 : .NET 8.0.0 (8.0.23.42311), X64 RyuJIT AVX2
  8.0.0-rc.2.23426.4      : .NET 8.0.0 (8.0.23.42311), X64 RyuJIT AVX2

Regression?

Data

Method Runtime NuGetReferences Mean Error StdDev Ratio
CreateA .NET 7.0 Microsoft.Extensions.DependencyInjection 7.0.0 122.1 ns 2.05 ns 1.92 ns baseline
CreateA .NET 8.0 Microsoft.Extensions.DependencyInjection 8.0.0-preview.6.23329.7 116.8 ns 2.33 ns 2.50 ns -4%
CreateA .NET 8.0 Microsoft.Extensions.DependencyInjection 8.0.0-preview.7.23375.6 113.6 ns 1.54 ns 1.44 ns -7%
CreateA .NET 8.0 Microsoft.Extensions.DependencyInjection 8.0.0-rc.2.23426.4 119.0 ns 2.42 ns 2.69 ns -3%
CreateB .NET 7.0 Microsoft.Extensions.DependencyInjection 7.0.0 220.3 ns 3.31 ns 3.10 ns baseline
CreateB .NET 8.0 Microsoft.Extensions.DependencyInjection 8.0.0-preview.6.23329.7 218.9 ns 3.83 ns 3.39 ns -1%
CreateB .NET 8.0 Microsoft.Extensions.DependencyInjection 8.0.0-preview.7.23375.6 480.0 ns 3.31 ns 2.76 ns +118%
CreateB .NET 8.0 Microsoft.Extensions.DependencyInjection 8.0.0-rc.2.23426.4 402.6 ns 4.53 ns 4.24 ns +83%
CreateC .NET 7.0 Microsoft.Extensions.DependencyInjection 7.0.0 522.9 ns 4.21 ns 3.73 ns baseline
CreateC .NET 8.0 Microsoft.Extensions.DependencyInjection 8.0.0-preview.6.23329.7 494.1 ns 3.49 ns 2.91 ns -5%
CreateC .NET 8.0 Microsoft.Extensions.DependencyInjection 8.0.0-preview.7.23375.6 2,489.0 ns 26.57 ns 24.85 ns +376%
CreateC .NET 8.0 Microsoft.Extensions.DependencyInjection 8.0.0-rc.2.23426.4 1,604.6 ns 26.34 ns 23.35 ns +207%
Author: Jakimar
Assignees: -
Labels:

tenet-performance, untriaged, area-Extensions-DependencyInjection, needs-area-label

Milestone: -

@steveharter
Copy link
Member

Currently verifying this issue to see if same as reported in #89104

@steveharter steveharter removed the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Aug 28, 2023
@steveharter
Copy link
Member

steveharter commented Aug 28, 2023

This is a regression from the "keyed services" feature due to reading custom attributes. In this case, it is the FromKeyedServicesAttribute per ParameterInfo which in a quick prototype showed that caching this in a dictionary will get very close to 7.0 perf. Working on this now.

@steveharter steveharter added needs-further-triage Issue has been initially triaged, but needs deeper consideration or reconsideration and removed untriaged New issue has not been triaged by the area owner labels Aug 28, 2023
@steveharter
Copy link
Member

Using a dictionary to hold the ParameterInfo per above does get us back to almost 7.0, but doing a full cache on all of the ConstructorInfo state yields a much better result:

7.0:

Method Mean Error StdDev Median Min Max Gen0 Allocated
CreateA 147.9 ns 1.07 ns 0.95 ns 148.1 ns 146.0 ns 149.3 ns 0.0198 208 B
CreateB 249.1 ns 2.42 ns 2.15 ns 249.7 ns 245.1 ns 251.8 ns 0.0405 432 B
CreateC 566.5 ns 2.64 ns 2.21 ns 567.0 ns 560.4 ns 569.2 ns 0.0610 648 B

8.0:

Method Mean Error StdDev Median Min Max Gen0 Allocated
CreateA 134.5 ns 1.13 ns 1.00 ns 134.5 ns 133.2 ns 136.2 ns 0.0196 208 B
CreateB 473.9 ns 6.06 ns 5.06 ns 471.3 ns 467.9 ns 481.4 ns 0.0386 456 B
CreateC 1,929.6 ns 28.26 ns 25.05 ns 1,927.4 ns 1,895.7 ns 1,978.6 ns 0.0484 600 B

After:

Method Mean Error StdDev Median Min Max Gen0 Allocated
CreateA 63.17 ns 1.224 ns 1.144 ns 62.87 ns 61.90 ns 64.92 ns 0.0167 176 B
CreateB 156.20 ns 1.943 ns 1.722 ns 155.95 ns 153.78 ns 159.85 ns 0.0374 392 B
CreateC 444.48 ns 3.875 ns 3.435 ns 443.92 ns 438.29 ns 451.37 ns 0.0461 496 B

@steveharter
Copy link
Member

Setting to v9; will get feedback on whether to port to v8.

@steveharter steveharter added this to the 9.0.0 milestone Aug 29, 2023
@steveharter steveharter removed the needs-further-triage Issue has been initially triaged, but needs deeper consideration or reconsideration label Aug 29, 2023
@steveharter steveharter self-assigned this Aug 29, 2023
@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label Aug 29, 2023
@steveharter steveharter changed the title NET 8.0 Preview 7 Perfomance Regression: ActivatorUtilities.CreateInstance NET 8.0 Preview 7 Performance Regression: ActivatorUtilities.CreateInstance Aug 29, 2023
@stephentoub
Copy link
Member

Setting to v9; will get feedback on whether to port to v8.

I think it'd be good to fix this for 8.

@steveharter steveharter modified the milestones: 9.0.0, 8.0.0 Sep 1, 2023
@ghost ghost removed the in-pr There is an active PR which will close this issue when it is merged label Sep 11, 2023
@stephentoub
Copy link
Member

Reopening to track backport.

@steveharter
Copy link
Member

Closing; fixed in backport

@ghost ghost locked as resolved and limited conversation to collaborators Oct 12, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants