Skip to content

Fast repeated resolution of dependency injection services  #36385

@pakrym

Description

@pakrym

Often in ASP.NET Core application type is being repeatedly resolved at runtime but is not known at compile time and couldn't be made a true constructor injected dependency.

Some examples:

  1. Middleware activation
  2. Everything that uses ActivatorUtilities

All these usages are forced to call GetService<>() that incurs a resolver lookup cost for every call that might be proportionally very large in case of simple service type/singletons and in general very efficient resolvers.

Proposal

Add 2 types:

    public interface IServiceActivator
    {
        object GetService(IServiceProvider provider);
    }

    public interface IServiceActivatorFactory
    {
        IServiceActivator Create(Type serviceType);
    }

IServiceActivatorFactory should be automatically registered in IServiceProvider implementations (same as IServiceScopeProvider).

IServiceActivatorFactory.Create would return an IServerActivator instance that activates services using provided IServerProvider (IServiceProvider has to be passed in to support resolution from IServiceScope's).

IServiceActivatorFactory.Create would return null if service is not registered.

IServiceActivatorFactory.Create is free to throw when service is registered but it's construction is impossible (dependencies not satisfied, etc.)

IServiceProvider passed into IServiceActivator.GetService is expected to be the same IServiceActivatorFactory was resolved from or child scope thereof. Implementation is free to validate this assumption but said validation is not required.

IServiceActivatorFactory.Create should support resolution all the types GetService does.

3rd party containers and adoption planning

This feature would become a part of spec tests but all usages in ASP.NET Core 3.x would be resilient to IServiceActivatorFactory not being available in container (it's very easy to fallback to () => GetService(type)).

It is very easy for container implementations to add a trivial implemenations too.

Benchmarks

Quick implementation in MEDI showed 2-3x performance improvement in ActivatorUtilities performance

                  Method | UseActivatorFactory |      Mean |      Error |     StdDev |         Op/s |  Gen 0 | Allocated |
------------------------ |-------------------- |----------:|-----------:|-----------:|-------------:|-------:|----------:|
         ServiceProvider |               False |  36.74 ns |  0.3555 ns |  0.3325 ns | 27,219,949.5 | 0.0001 |      24 B |
                 Factory |               False |  44.27 ns |  0.8717 ns |  0.8154 ns | 22,586,712.3 | 0.0001 |      24 B |
 Factory3ArgumentsFromDI |               False | 106.25 ns |  1.3168 ns |  1.0996 ns |  9,411,498.7 | 0.0001 |      24 B |
          CreateInstance |               False | 839.81 ns | 15.6118 ns | 15.3328 ns |  1,190,745.0 | 0.0010 |     328 B |
         ServiceProvider |                True |  38.78 ns |  0.2703 ns |  0.2529 ns | 25,787,513.2 | 0.0001 |      24 B |
                 Factory |                True |  17.44 ns |  0.1417 ns |  0.1256 ns | 57,335,732.4 | 0.0001 |      24 B |
 Factory3ArgumentsFromDI |                True |  37.36 ns |  0.3099 ns |  0.2747 ns | 26,767,283.6 | 0.0001 |      24 B |
          CreateInstance |                True | 877.28 ns | 17.8947 ns | 24.4944 ns |  1,139,887.2 | 0.0010 |     328 B |

Prototype is in https://github.com/aspnet/Extensions/tree/pakrym/IServiceActivator

cc @davidfowl @rynowak

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions