-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
For perf, use the new ConstructorInvoker APIs for ActivatorUtilities.CreateFactory #89573
Conversation
Tagging subscribers to this area: @dotnet/area-extensions-dependencyinjection Issue Details[verifying tests]
|
@@ -179,7 +180,7 @@ public void CreateInstance_ClassWithABC_MultipleCtorsWithSameLength_ThrowsAmbigu | |||
} | |||
|
|||
[Fact] | |||
public void CreateFactory_CreatesFactoryMethod() | |||
public void CreateFactory_CreatesFactoryMethod_4Types_3Injected() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This new test was added for coverage; the other new code paths already had coverage.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have any perf numbers (both throughput and size) for this change?
src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ActivatorUtilities.cs
Outdated
Show resolved
Hide resolved
src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ActivatorUtilities.cs
Outdated
Show resolved
Hide resolved
src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ActivatorUtilities.cs
Outdated
Show resolved
Hide resolved
src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ActivatorUtilities.cs
Outdated
Show resolved
Hide resolved
src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ActivatorUtilities.cs
Outdated
Show resolved
Hide resolved
...s/Microsoft.Extensions.DependencyInjection.Abstractions/src/ActivatorUtilities.Reflection.cs
Outdated
Show resolved
Hide resolved
...s/Microsoft.Extensions.DependencyInjection.Abstractions/src/ActivatorUtilities.Reflection.cs
Outdated
Show resolved
Hide resolved
...s/Microsoft.Extensions.DependencyInjection.Abstractions/src/ActivatorUtilities.Reflection.cs
Outdated
Show resolved
Hide resolved
...s/Microsoft.Extensions.DependencyInjection.Abstractions/src/ActivatorUtilities.Reflection.cs
Outdated
Show resolved
Hide resolved
...s/Microsoft.Extensions.DependencyInjection.Abstractions/src/ActivatorUtilities.Reflection.cs
Outdated
Show resolved
Hide resolved
if (serviceProvider is null) | ||
ThrowHelperArgumentNullExceptionServiceProvider(); | ||
|
||
object?[] constructorArguments = new object?[parameters.Length]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not saying we should but we could pool this array now that we have Span support.
src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ActivatorUtilities.cs
Outdated
Show resolved
Hide resolved
src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ActivatorUtilities.cs
Outdated
Show resolved
Hide resolved
See #85065 for the previous size gains of removing Linq.Expressions assembly (went from ~260k+ to 4k) when See #66153 which has additional details on CPU gains. Reflection APIs are much faster in general, and the DI CreateFactory is ~1.5x faster depending on the scenario, but still slower than using Linq expressions. We could investigate further as the Blazor gains were not as much as the Windows gains, percentage-wise. |
if (serviceProvider is null) | ||
ThrowHelperArgumentNullExceptionServiceProvider(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NIT: Since it is within .NET 8 if-def could we use ArgumentNullException.ThrowIfNull(serviceProvider)
? Here and 4 similar cases below
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah there was an earlier discussion around this - it's at least consistent now across all cases using the helper method. In one case, under .NET 8, we couldn't use ThrowIfNull due to a compile warning around unused variable (but we still want to verify for consistency) and of course under .NetStandard.NetFx we can't use ThrowIfNull at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left a NIT, overall LGTM thanks!
This change badly broke DI for native AOT. Number of DI tests are failing on native AOT in this repo, and it soon going to affect ASP.NET and perflab. I think we need to revert this change.
|
Fixes #66153 which also contains additional information on benchmarks here.
This PR changes DI to use the new zero-alloc invoke APIs and is expected to close out the DI+Blazor perf work for v8 -- Blazor apps can use DI
ActivatorUtilities.CreateFactory
without bringing in the largeSystem.Linq.Expressions
assembly and in a performant manner (but not quite as fast as having Blazor use Linq Expressions instead of reflection).In summary, for Blazor, this PR makes CreateFactory ~1.3-1.5x faster for common "fast paths" measured under a Blazor client app. However, when run under CorClr+Windows, there is a much larger ~2-3x gain. The difference appears to be additional overhead in Mono interpreter lambda methods with variable capture. For NativeAOT, this PR is expected to make CreateFactory also around ~1.5x faster based on reflection methods being 1.3x-1.7x faster with the new zero-alloc APIs.
Background:
System.Linq.Expressions
assembly.System.Linq.Expressions
assembly. Also reflection in general was changed to use emit under Blazor getting from 1.2x to 2x improvement depending on scenario (property setters and zero-arg ctor had the best perf gains).Note the reflection path is used (vs. Expressions) when
RuntimeFeature.IsDynamicCodeCompiled == false
which will be the case for Blazor client and NativeAOT . For NativeAOT, using expressions would cause them to be interpreted for NativeAOT which is very slow, and for Blazor using expressions would bring along the very large System.Linq.Expressions assembly where a smaller (trimmed) size is important. Note that this PR does not change the semantics here (that was done previously -- see the links above); only the CPU performance is improved.