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
Optimize ServiceCollectionDescriptorExtensions TryAdd #44728
Comments
Tagging subscribers to this area: @eerhardt, @maryamariyan Issue Details
|
Hi, I would ask some questions if this issue is allowed to improve now, thanks.
Based on conversation, should ServiceCollection add new Dictionary<ServiceType, collection of serviceDescriber> field and keep old List field? This will have synchronous action when calling modified related method for example RemoveAll() ? |
@stephentoub In answer to your question #44696 (comment)
Looking at the startup call counts; I'm going to say yes... Can see it growing per call (not completely in order) |
We should look into this as part of 6.0.0 in the name of startup performance. It'll also require a new API which we should try to get approved. |
Assigning to myself to do. |
I can think of 2 options here:
I prefer option 2 as it avoids interface dispatch and avoids introducing a new type. namespace Microsoft.Extensions.DependencyInjection
{
public class ServiceCollection
{
+ public void TryAdd(ServiceDescriptor descriptor);
+ public void TryAddEnumerable(ServiceDescriptor descriptor);
+ public void Replace(ServiceDescriptor descriptor);
+ public void RemoveAll(Type serviceType);
}
} Thoughts @eerhardt and @maryamariyan? |
How common is it for others to implement their own What do you think about option 3:
|
Option 3 sounds fine as well but my hottake is that nobody writes their own IServiceCollection so it's not worth it. |
Ok, then Option 2 sounds good to me. |
OK, I've updated the proposal to include the API |
LGTM. Just some thoughts for API review: Even though the existing Lines 67 to 75 in eeb185f
don't return bool, would we want to make these new ones return bool? |
Yep. I'd wager that 98% of use cases are ServiceCollection instances. Though I don't have a problem trying to make this work for Lamar as well. |
They would have to change their inheritect for OTOH if a default interface was also added (and public interface IServiceCollection : IList<ServiceDescriptor>
{
#if NETCOREAPP3_1_OR_GREATER
public bool TryAdd(ServiceDescriptor descriptor) => ServiceCollectionDescriptorExtensions.TryAdd(this, descriptor);
public bool TryAddEnumerable(ServiceDescriptor descriptor) => ServiceCollectionDescriptorExtensions.TryAddEnumerable(this, descriptor);
public bool TryReplace(ServiceDescriptor descriptor) => ServiceCollectionDescriptorExtensions.TryReplace(this, descriptor);
public void RemoveAll(Type serviceType) => ServiceCollectionDescriptorExtensions.RemoveAll(this, serviceType);
#endif
} The extensions can still have a typecheck for |
Right, I don't think there's an either or here. We should do the move and the cast anyways. We can use DIMs on IServiceCollection as well on .NET Core specific frameworks. I'm just saying that besides Lamar, I don't think anyone extends this type and it forces them to cross compile for the performance boost anyways. As long as that's fine, we can do both things. |
Checking implementations using grep.app serach : IServiceCollection for C# are a few implementations and extensions:
Microsoft.Azure.WebJobs.Host/Hosting/TrackedServiceCollection.cs internal class TrackedServiceCollection : IServiceCollection, ITrackedServiceCollection
Hosting/IMauiServiceCollection.cs public interface IMauiServiceCollection : IServiceCollection Hosting/Internal/MauiServiceCollection.cs class MauiServiceCollection : IMauiServiceCollection Hosting/IMauiHandlersCollection.cs public interface IMauiHandlersCollection : IMauiServiceCollection Hosting/ImageSources/IImageSourceServiceCollection.cs public interface IImageSourceServiceCollection : IServiceCollection etc
OrchardCore/Shell/Builders/FeatureAwareServiceCollection.cs public class FeatureAwareServiceCollection : IServiceCollection
SevenTiny.Bantina.Spring/DependencyInjection/ServiceCollection.cs public class ServiceCollection : IServiceCollection
public interface IAutoHealthCheckBuilder : IServiceCollection
internal class ServiceCollection : IServiceCollection
public class ServiceCollection : IServiceCollection
public class ServiceCollection : IServiceCollection |
Also a bunch that don't start |
Check their target frameworks as well |
Ah, get you, you mean if they target OTH they wouldn't get the performance improvement anyway as they don't inherit from |
Approved as proposed. namespace Microsoft.Extensions.DependencyInjection
{
public class ServiceCollection
{
+ public bool TryAdd(ServiceDescriptor descriptor);
+ public bool TryAddEnumerable(ServiceDescriptor descriptor);
+ public bool TryReplace(ServiceDescriptor descriptor);
+ public void RemoveAll(Type serviceType);
}
} |
We decided not to add new APIs here. We moved the type and are keeping these internal for now, |
Should we close this issue then? |
Moving a type to a different assembly is still an "API" change, right? |
That's fair. |
@davidfowl I am one of those that implement their own IServiceCollection. It is not uncommon when creating non-MS DI integrations. My IServiceCollection wraps my DI's container's builder, to expose 'fake' service registrations out of it, so it 'looks like' services registered outside of MS DI are present. Because so many MS libraries try to call TryAdd* and then duplicate crap if they don't see it already present. Without this, I end up with a billion registrations of IOptions stuff, for instance. If there were Try* methods available to me, I'd implement those directly on my IServiceCollection, instead of exposing a manufactured fake list of stuff. |
See the conversation here: https://github.com/dotnet/runtime/pull/44696/files#r523781023
We are currently looping over the IServiceCollection looking if a ServiceDescriptor has already been added for this ServiceType. Not only are we going through the whole collection for each new service added, but we are also making interface dispatches for each element.
Instead, we should introduce a new interface that
ServiceCollection
implements, so we can fast path this check.While introducing this new interface, we should analyze if there are other paths on the startup path that can be optimized as well, in case we need other new methods.
cc @davidfowl @stephentoub @benaadams
Proposed API
The text was updated successfully, but these errors were encountered: