Runtime: API proposal: Activator.CreateFactory and ConstructorInfo.CreateDelegate

Created on 11 May 2020  路  27Comments  路  Source: dotnet/runtime

(See also https://github.com/dotnet/runtime/pull/32520.)

API proposal

namespace System
{
    public static class Activator
    {
      // EXISTING APIs (abridged):
      public static T CreateInstance<T>();
      public static object? CreateInstance(Type type);
      public static object? CreateInstance(Type type, bool nonPublic);

      // NEW PROPOSED APIs:
      public static Func<T> CreateFactory<T>(); // no new() constraint, matches existing API
      public static Func<object?> CreateFactory(Type type);
      public static Func<object?> CreateFactory(Type type, bool nonPublic);
    }
}

// STRETCH GOAL APIs (see end of proposal):
namespace System.Reflection
{
    public class ConstructorInfo
    {
      public static TDelegate CreateDelegate<TDelegate>();
      public static Delegate CreateDelegate(Type delegateType);
    }
}

// REMOVED FROM PROPOSAL
/*
namespace System.Runtime.CompilerServices
{
    public static class RuntimeHelpers
    {
        // EXISTING API:
        public static object GetUninitializedObject(Type type);

        // NEW PROPOSED API:
        public static Func<object> GetUninitializedObjectFactory(Type type);
    }
}
*/

Discussion

Instantiating objects whose types are not known until runtime is fairly common practice among frameworks and libraries. Deserializers perform this task frequently. Web frameworks and DI frameworks might create per-request instances of objects, then destroy those objects at the end of the request.

APIs like Activator.CreateInstance and the _System.Reflection_ surface area can help with this to a large extent. However, those are sometimes seen as heavyweight solutions. We've built some caching mechanisms into the framework to suppose these use cases. But it's still fairly common for high-performance frameworks to bypass Activator and the reflection stack and to go straight to manual codegen. See below for some examples.

See below for some concrete samples.

System.Text.Json

https://github.com/dotnet/runtime/blob/9d85b82e3ad856068459c8f8bba8509e038afb40/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs#L39-L51

ASP.NET Core

https://github.com/dotnet/aspnetcore/pull/14615 (though they're using TypeBuilder to work around this right now)

Other runtime + libraries

https://github.com/dotnet/runtime/blob/9d85b82e3ad856068459c8f8bba8509e038afb40/src/libraries/System.ComponentModel.Composition/src/System/ComponentModel/Composition/MetadataViewGenerator.cs#L371-L380

Ref emit incurs a substantial upfront perf hit, but it does generally provide a win amortized over the lifetime of the cached method as it's invoked over and over again. However, this comes with its own set of problems. It's difficult for developers to get the exact IL correct across the myriad edge cases that might exist. It's not very memory-efficient. And as runtimes that don't allow codegen become more commonplace, it complicates the callers' code to have to decide the best course of action to take for any given runtime.

These proposed APIs attempt to solve the problem of creating a basic object factory using the best mechanism applicable to the current runtime. The exact mechanism used can vary based on runtime: perhaps it's codegen, perhaps it's reflection, perhaps it's something else. But the idea is that the performance of these APIs should rival the best hand-rolled implementations that library authors can create.

Shortcomings, not solved here

This API is not a panacea to address all performance concerns developers have with the reflection stack. For example, this won't change the perf characteristics of MethodInfo.Invoke. But it could be used to speed up the existing Activator.CreateInstance<T> APIs and to make other targeted improvements. In general, this API provides an alternative pattern that developers can use so that they don't have to roll solutions themselves.

It also does not fully address the concern of calls to _parameterized_ ctors, such as you might find in DI systems. The API RuntimeHelpers.GetUninitializedObjectFactory does help with this to some extent. The caller can cache that factory to instantiate "blank" objects quickly, then use whatever existing fast mechanism they wish to call the object's real ctor over the newly allocated instance.

I hope to have a better solution for this specific scenario in a future issue.

Stretch goal APIs

The APIs ConstructorInfo.CreateDelegate<TDelegate>() and friends are meant to allow invoking parameterless or parameterful ctors. These are primarily useful when the objects you're constructing have a common signature in their constructors, but you don't know the actual type of the object at compile time.

Consider:

public class MyService
{
    public MyService(IServiceProvider serviceProvider) { }
}

public class MyOtherService
{
    public MyOtherService(IServiceProvider serviceProvider) { }
}

In these cases, the caller would get the ConstructorInfo they care about, then call constructorInfo.CreateDelegate<Func<IServiceProvider, object>>(). Depending on which ConstructorInfo was provided, the returned delegate will create either a MyService or a MyOtherService, calling the appropriate ctor with the caller-provided IServiceProvider.

I say "stretch goal" because the parameter shuffling involved here would involve spinning up the JIT. We can keep this overhead to a minimum because within the runtime we can take shortcuts that non-runtime components can't take with the typical DynamicMethod-based way of doing things. So while there's _less_ JIT cost compared to a standard DynamicMethod-based solution, there's still _some_ JIT cost, so it doesn't fully eliminate startup overhead. (This overhead isn't much different than the overhead the runtime already incurs when it sees code like Func<string, bool> func = string.IsNullOrEmpty;, since the runtime already needs to create a parameter shuffling thunk for this scenario.)

api-approved area-System.Reflection

Most helpful comment

IIRC those guidelines only affect the public API surface, not any internal usage. And honestly I'm not a huge fan of them, as after the Katana experiment and dealing with Func<string, int, Action<object, ...>, ...> everywhere you begin to long for delegate types where the delegate type names have meaning and the invoke method parameters have actual names.

All 27 comments

Yeah, that's the part I'm having trouble with. What you're talking about is the more general case of fast method invocation. I'm blanking right now on a good, reliable, high-performance way to do that without involving codegen. Right now MethodInfo.Invoke is the best tool to suit that need due to how extremely flexible it is. But of course this flexibility comes at a price, and many callers don't want to pay the price for the flexibility they don't need. Aspnet is a good case study on this. :)

Let's assume for the moment that you had a good way of invoking arbitrary instance methods in a high-performance fashion. The APIs proposed here would allow you to extend that same concept to constructors of arbitrary signature. To do this, you'd use the proposed RuntimeHelpers.GetUninitializedObjectFactory API to get a zero-inited instance of the target object, where no ctors have been run. Then you'd invoke the ctor as you would any other instance method.

__Edit:__ As an example of what I said above, assume that the pattern you wanted to support instantiating was .ctor(IServiceProvider). To do that on top of this API, your factory helper would look like the below.

public class MyFactory
{
    private Func<object> _allocator;
    private Action<object, IServiceProvider> _ctor;

    public MyFactory(Type targetType)
    {
        if (targetType.IsValueType) { /* throw */ }
        _allocator = RuntimeHelpers.GetUninitializedObjectFactory(targetType);
        MethodInfo mi = targetType.GetMethod(".ctor", new Type[] { typeof(IServiceProvider) });
        _ctor = (Action<object, IServiceProvider>)mi.CreateDelegate(typeof(Action<object, IServiceProvider>), null /* instance */);
    }

    public object CreateInstance(IServiceProvider serviceProvider)
    {
        object instance = _allocator(); // these two calls together are basically 'newobj'
        _ctor(instance, serviceProvider);
        return instance;
    }
}

In this scenario there's _zero_ codegen involved. If you wanted to support value types it gets a bit more complicated. Maybe that's something we could help with here.

public static Func<T> CreateFactory<T>();

I'm trying to understand the benefit of this over Activator.CreateInstance<T>(). If the above yields significant improvements, why wouldn't Activator.CreateInstance<T>() be changed to the equivalent of:
C# private static class Cache<T> { public static readonly Func<T> Func = CreateFactory<T>(); } public static T CreateInstance<T>() => Cache<T>.Func();
?

I'm sure I'm just missing something.

I do plan on adding some of the existing Activator code paths to take advantage of this. But there's still considerable indirection while the activator tries to look up the appropriate factory method. In my experimentation this indirection alone was twice the cost of instantiating the target object.

For applications which maintain their own caching mechanisms we can return to them a factory which bypasses all of the built in indirection. This is the most desirable from the perspective of giving them bare metal performance so that there's no need for them to use their own codegen.

Edit: activator doesn't even initialize the cache until the second call to the constructor. The reason is that we want to optimize for the case where the type is instantiated only once, perhaps as part of app startup.

CreateFactory(Type type, bool nonPublic)

Do we also need an overload without nonPublic argument?

(Also, we need to be thinking about the linker annotations when introducing new reflection APIs.)

I'm trying to understand the benefit of this over Activator.CreateInstance()

For the generic overload, the fundamental advantage of the factory method compared to Activator.CreateInstance<T> is that Activator.CreateInstance<T> wraps exceptions in TargetInvocationException that adds an extra frame with EH. The factory method avoids this overhead. To get rid of this overhead, we would need to do a breaking change to stop wrapping exceptions in TargetInvocationException.

For the non-generic overload, the additional fundamental advantage of the factory method compared to Activator.CreateInstance are the indirections from the caching policy. The factory method avoids this overhead by moving the caching policy to the application. To get rid of this overhead, we would need to do a non-pay-for-play caching policy that would make all System.RuntimeType instances more expensive.

Do we also need an overload without nonPublic argument?

We probably should, for convenience and to help lessen confusion.

Also, we need to be thinking about the linker annotations when introducing new reflection APIs.

Do you have pointers on this topic? I don't see any annotations on the existing Activator.CreateInstance API, so I guess the linker itself is aware of the Activator APIs and special-cases calls to them?

Do you have pointers on this topic?

https://github.com/dotnet/runtime/issues/36229 . It is work in progress. These annotations were discussed during the API review meetings a few weeks ago.

Activator.CreateInstance wraps exceptions in TargetInvocationException that adds an extra frame with EH. The factory method avoids this overhead

I see. It's unfortunate we pay that overhead, in particular in the : new() constraint case, where the developer is just writing new T() and so a TargetInvocationException is even more unexpected. Would we recommend the C# compiler change its code gen to use the new APIs? Presumably not, for the same breaking change concern?

Presumably not, for the same breaking change concern?

Right. Second reason for not recommending this API to C# compiler is that Roslyn would need to generate a boilerplate to cache the delegate.

The factory method adds an extra frame as well compared to just calling new MyType(). It does not have EH, but it is still an extra frame. And the factory method pays an extra overhead for delegate invocation. I guess that the generic factory method may have no performance advantage compared to optimized Activator.CreateInstance<T>.

Using Activator.CreateInstance(Type, ...) is slightly faster than Activator.CreateInstance<T>() for reference types, but I think the strongly typed overload is still worth the convenience.

Activator.CreateInstance<T>() (where _T_ is a value type with an explicit ctor) allocates then unboxes. Activator.CreateFactory<T>() is allocation free for this scenario. But we might be able to perform whatever contortions we need to behind the scenes to make CreateInstance allocation free here as well.

Using Activator.CreateInstance(Type, ...) is slightly faster than Activator.CreateInstance<T>() for reference types

Only in specific situations, such as using the generic overload from another generic context? Otherwise I don't see that.. I just tried this:
```C#
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System;

[MemoryDiagnoser]
public class Program
{
static void Main(string[] args) => BenchmarkSwitcher.FromAssemblies(new[] { typeof(Program).Assembly }).Run(args);

[Benchmark] public Program A1() => Activator.CreateInstance<Program>();
[Benchmark] public object A2() => Activator.CreateInstance(typeof(Program));
[Benchmark] public Program A3() => (Program)Activator.CreateInstance(typeof(Program));

}
```
and got this:

| Method | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|------- |---------:|---------:|---------:|-------:|------:|------:|----------:|
| A1 | 28.89 ns | 0.246 ns | 0.205 ns | 0.0038 | - | - | 24 B |
| A2 | 30.03 ns | 0.512 ns | 0.479 ns | 0.0038 | - | - | 24 B |
| A3 | 30.17 ns | 0.257 ns | 0.241 ns | 0.0038 | - | - | 24 B |

Activator.CreateInstance<T>() (where T is a value type with an explicit ctor) allocates then unboxes.

Since you can't currently write such a T in C#, I'm not very concerned about optimizing for that case. It also seems like something we should be able to fix if it was really meaningful.

Using Activator.CreateInstance(Type, ...) is slightly faster than Activator.CreateInstance<T>() for reference types, but I think the strongly typed overload is still worth the convenience.

In practice should the comparison be with (T)Activator.CreateInstance(typeof(T), ...); since you would likely immediately cast object to something usable

Using Activator.CreateInstance(Type, ...) is slightly faster than Activator.CreateInstance<T>() for reference types

That is because of the current implementation of Activator.CreateInstance<T>() in CoreCLR leaves a lot of performance on the table. We can make it 2x-3x faster if we ported the implementation strategy for it from CoreRT.

E.g. run this:

class Program
{
    static void Main(string[] args)
    {
        for (;;)
        {
            {
                Stopwatch sw = new Stopwatch();
                sw.Start();
                for (int i = 0; i < 100000000; i++)
                    GC.KeepAlive(Activator.CreateInstance<object>());
                Console.WriteLine($"CreateInstance<T> {sw.ElapsedMilliseconds}");
            }

            {
                Type objType = typeof(object);
                Stopwatch sw = new Stopwatch();
                sw.Start();
                for (int i = 0; i < 100000000; i++)
                    GC.KeepAlive(Activator.CreateInstance(objType));
                Console.WriteLine($"CreateInstance(Type) {sw.ElapsedMilliseconds}");
            }
        }
    }
}

CoreCLR:

CreateInstance<T> 4249
CreateInstance(Type) 4035

CoreRT:

CreateInstance<T> 1514
CreateInstance(Type) 25089 // CoreRT does not have the activator cache like CoreCLR

Video

  • We don't add a new() constraint to match the existing API

```C#
namespace System
{
public partial class Activator
{
// Existing APIs
// public static T CreateInstance();
// public static object? CreateInstance(Type type);
// public static object? CreateInstance(Type type, bool nonPublic);

    public static Func<T> CreateFactory<T>();
    public static Func<object?> CreateFactory(Type type);
    public static Func<object?> CreateFactory(Type type, bool nonPublic);
}

}
namespace System.Reflection
{
public class ConstructorInfo
{
public static TDelegate CreateDelegate();
public static Delegate CreateDelegate(Type delegateType);
}
}
```

FWIW, we can play some games within ConstructorInfo.CreateDelegate to make it optimized beyond what normal consumers can do.

For example, say that you have these three call sites:

private object? MySharedDelegate(ref JsonSerializerOptions options);

Func<string, object> factory1 = ci1.CreateDelegate<Func<string, object>>();
Func<IServiceProvider, IService> factory2 = ci2.CreateDelegate<Func<IServiceProvider, IService>>();
MySharedDelegate factory3 = (MySharedDelegate)ci3.CreateDelegate(typeof(MySharedDelegate));

We'd only need to spin up the JIT _once_ to handle all of these cases, as our parameter shuffling thunk can be shared between all factory signatures. Normal ref emit-based consumers can't make this same optimization.

This API that we have discussed over email was the generic one. That one is fine and it can be dealt with in performance and reliable way in AOT scenarios that do not have fallback to JIT.

The API that takes the delegate as open System.Type does not have that property. Do we really need it?

@jkotas If there's AOT / JIT concern, we can hold off on that overload since nobody's asking for it just yet. It's only included for symmetry with other APIs like MethodInfo.CreateDelegate.

public static TDelegate CreateDelegate();

I guess this should be public virtual TDelegate CreateDelegate<TDelegate>()

Hmmm, generic virtual methods. I think you original idea with a method on Activator may be better.

With MethodInfo.CreateDelegate, the generic method is non-virtual, and the method which accepts a Type parameter is virtual. Solves the virtual generic dispatch problem.

The generic MethodInfo.CreateDelegate was added very recently for convenience.

The non-generic MethodInfo.CreateDelegate was added during the layering refactoring as a replacement for the static reflection methods on System.Delegate. These reflection methods on System.Delegate are the actual problem. Once the delegate is used with reflection, it is hard to predict the set of the various shuffling thunks that it is going to need so AOT w/o JIT fallback typically generates all of them conservatively.

I'll split this into two different PRs. The parameterless Activator one should be fairly straightforward since it'll be able to directly use the recent refactoring we made under the covers. The parameterful one would be significantly more complex anyway since it'll involve the JIT. We can always change the API surface (or remove it entirely!) based on how that prototype goes, then bring it back for API review before final commit.

Proposed behaviors

Parameterless factories

The non-generic method Activator.CreateInstance(Type) returns Func<object?>. To match the existing Activator.CreateInstance behavior, if the provided _type_ represents a Nullable<T>, the delegate will return _null_ (hence, Func<object?> instead of Func<object>). If _type_ represents a value type, the delegate will return a boxed instance of that value type. If the value type has an explicit parameterless ctor, it will be run.

The generic method Activator.Factory<T> returns Func<T>. If _T_ is a value type and has an explicit parameterless ctor, it will be run; otherwise for value types the factory will return _default(T)_.

Pointers, byrefs, open generics, and similar nonsense is blocked in all cases.

Parameterful factories

The caller specifies the type of the delegate they wish the factory to return. The delegate's arguments must be signature-compatible with the ctor's arguments. For example, if the constructor is typed as .ctor(object, int), then the delegates Func<string, int, ...> and Func<object, int, ...> are both compatible. No type-conversion, boxing, or unboxing is performed. So the delegates Func<object, byte, ...>, Func<object, uint, ...>, Func<object, object, ...>, and Func<int, int, ...> will be rejected.

Ctor arguments which take byrefs, ref structs, and pointers are possible through use of a custom delegate type, since Func<...> cannot be bound over such types. For instance:

public class MyClass
{
    public MyClass(byte* pBuf, nuint cbBuf) { }
}

delegate object MyDelegate(byte*, nuint);
MyDelegate factory = constructorInfo.CreateDelegate<MyDelegate>();

The factory delegate must have a non-void return type. The actual class type must be assignable-to the requested return type. In practice, this means that the return type can be the declaring type, object, any superclass of the declaring type, or any interface implemented by the declaring type. Boxing of value types is allowed in the return.

Note: The return type restrictions are a little looser than the restrictions on MethodInfo.CreateDelegate. This is due to the unique nature of the factories expected to be used in DI and other scenarios, where such restrictions might impose hardship on the callers. This flexibility does have possible impact on AOT scenarios, which might have to generate _two_ stubs (one boxing, one unboxing) pessimistically.

Getting _System.Text.Json_ to use this new API would benefit from https://github.com/dotnet/runtime/issues/4556#issuecomment-736172061 being resolved. The APIs proposed here return Func<object?>, but _System.Text.Json_ has its own signature-compatible delegate type that it uses everywhere. Working around the issue is possible but not desirable from a code cleanliness / maintainability perspective.

System.Text.Json has its own signature-compatible delegate type that it uses everywhere

Is it used in the public surface or just internally?

FWIW, .NET design guidelines suggest to use Action/Func as much as possible. If we do not like Action/Func from a code cleanliness / maintainability perspective, it sounds like a problem in the design guidelines.

IIRC those guidelines only affect the public API surface, not any internal usage. And honestly I'm not a huge fan of them, as after the Katana experiment and dealing with Func<string, int, Action<object, ...>, ...> everywhere you begin to long for delegate types where the delegate type names have meaning and the invoke method parameters have actual names.

Right, the guidelines talk about public APIs, but they indirectly affect what natural internal implementations looks like in many cases. Using different guidelines for internal implementations would often be inconsistent and add unnecessary overhead from back-and-forth conversions.

You can reduce the burden of repeating the full signature everywhere via using ConstructorDelegate = System.Func<object>; at the top of the file. I know that it is not perfect since you have to do it in every file today.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

bencz picture bencz  路  3Comments

EgorBo picture EgorBo  路  3Comments

Timovzl picture Timovzl  路  3Comments

yahorsi picture yahorsi  路  3Comments

sahithreddyk picture sahithreddyk  路  3Comments