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

Have ConstructorInfo use Activator.CreateInstance() instead of emit for default constructors #78917

Open
Tracked by #75358
steveharter opened this issue Nov 28, 2022 · 2 comments
Assignees
Labels
area-System.Reflection Cost:M Work that requires one engineer up to 2 weeks tenet-performance Performance related issue
Milestone

Comments

@steveharter
Copy link
Member

steveharter commented Nov 28, 2022

Activator.CreateInstance() was optimized in v6 and made super fast when invoking the default constructor. As shown in the benchmarks below, currently calling a zero-parameter constructor through Activator.CreateInstance(...) is ~1.7x faster than ConstructorInfo. Note that before the 7.0 feature to use IL emit, Activator.CreateInstance(...) used to be ~6x faster than ConstructorInfo.

|                                                                       Method |        Job |              Toolchain |       Mean |      Error |     StdDev |     Median |        Min |        Max | Ratio | RatioSD |  Gen 0 | Allocated | Alloc Ratio |
|----------------------------------------------------------------------------- |----------- |----------------------- |-----------:|-----------:|-----------:|-----------:|-----------:|-----------:|------:|--------:|-------:|----------:|------------:|
|                                       Ctor0_ActivatorCreateInstance_NoParams | Job-VAFQFP | \newinvoke\corerun.exe |   8.700 ns |  0.1857 ns |  0.1646 ns |   8.733 ns |   8.395 ns |   8.999 ns |  1.00 |    0.00 | 0.0054 |      56 B |        1.00 |
|                                                               Ctor0_NoParams | Job-VAFQFP | \newinvoke\corerun.exe |  14.928 ns |  0.5837 ns |  0.6721 ns |  14.697 ns |  14.234 ns |  16.242 ns |  1.00 |    0.00 | 0.0053 |      56 B |        1.00 |

Although Activator is faster than ConstructorInfo that only applies for cases when there is a default constructor. When there are constructor parameters, calling ConstructorInfo is up to 2x faster.

Due to the varying performance of these two APIs, reflection users that need to invoke constructors and want the maximum performance currently need to call ConstructorInfo when there are parameters and Activator when there are no parameters. This issue should make this choice go away by making ConstructorInfo the preferred API going forward for almost all cases by having ConstructorInfo call Activator for default constructors. This also avoids generating IL for these cases which can help with start-up and reduce global memory usage.

Using ConstructorInfo instead of Activator also has the advantage of forcing the caller select the appropriate constructor ahead of time, instead of auto-selecting each time the method is called by inspecting the parameters. If the caller does not know what constructor to call, then continuing to use Activator is fine.

We also need to look at normalizing the exception handling between these two APIs so they have the same Exception semantics for OutOfMemoryException.

Also see #36194 for ideas on exposing "Factory" overloads that will not throw `TargetInvocationException".

@steveharter steveharter added this to the 8.0.0 milestone Nov 28, 2022
@steveharter steveharter self-assigned this Nov 28, 2022
@ghost
Copy link

ghost commented Nov 28, 2022

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

Issue Details

Activator.CreateInstance() was optimized in v6 and made super fast when invoking the default constructor. However, as shown in the benchmarks below, currently calling a zero parameter constructor through Activator.CreateInstance(...) is ~1.7x faster than ConstructorInfo. Note that before the 7.0 feature to use IL emit, Activator.CreateInstance(...) used to be ~6x faster than ConstructorInfo.

|                                                                       Method |        Job |              Toolchain |       Mean |      Error |     StdDev |     Median |        Min |        Max | Ratio | RatioSD |  Gen 0 | Allocated | Alloc Ratio |
|----------------------------------------------------------------------------- |----------- |----------------------- |-----------:|-----------:|-----------:|-----------:|-----------:|-----------:|------:|--------:|-------:|----------:|------------:|
|                                       Ctor0_ActivatorCreateInstance_NoParams | Job-VAFQFP | \newinvoke\corerun.exe |   8.700 ns |  0.1857 ns |  0.1646 ns |   8.733 ns |   8.395 ns |   8.999 ns |  1.00 |    0.00 | 0.0054 |      56 B |        1.00 |
|                                                               Ctor0_NoParams | Job-VAFQFP | \newinvoke\corerun.exe |  14.928 ns |  0.5837 ns |  0.6721 ns |  14.697 ns |  14.234 ns |  16.242 ns |  1.00 |    0.00 | 0.0053 |      56 B |        1.00 |

Although Activator is faster than ConstructorInfo that only applies for cases when there is a default constructor. When there are constructor parameters, calling ConstructorInfo is up to 2x faster.

Due to the varying performance of these two APIs, reflection users that need to invoke constructors and want the maximum performance currently need to call ConstructorInfo when there are parameters and Activator when there are no parameters. This issue should make this choice go away by making ConstructorInfo the preferred API going forward for almost all cases by having ConstructorInfo call Activator for default constructors. This also avoids generating IL for these cases which can help with start-up and reduce global memory usage.

Using ConstructorInfo instead of Activator also has the advantage of forcing the caller select the appropriate constructor ahead of time, instead of auto-selecting each time the method is called by inspecting the parameters. If the caller does not know what constructor to call, then continuing to use Activator is fine.

We also need to look at normalizing the exception handling between these two APIs so they have the same Exception semantics for OutOfMemoryException.

Author: steveharter
Assignees: steveharter
Labels:

area-System.Reflection, tenet-performance

Milestone: 8.0.0

@steveharter
Copy link
Member Author

Moving to 9.0.

Note that the existing ConstructorInfo invoke API was made ~1.2x faster with this PR: #88415 which puts it much closer to Activator.CreateInstance (minus the overhead of emit).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-System.Reflection Cost:M Work that requires one engineer up to 2 weeks tenet-performance Performance related issue
Projects
None yet
Development

No branches or pull requests

1 participant