Skip to content

Commit a3bcc10

Browse files
committed
Performance improvement when making registrations based on generic service types. Fixes #985
1 parent d3bbf74 commit a3bcc10

File tree

1 file changed

+149
-8
lines changed

1 file changed

+149
-8
lines changed

src/SimpleInjector/Internals/GenericRegistrationEntry.cs

Lines changed: 149 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ internal sealed class GenericRegistrationEntry : IRegistrationEntry
1212
private readonly List<IProducerProvider> providers = new();
1313
private readonly ContainerOptions options;
1414

15+
// PERF: #985 These two collections exist solely for performance optimizations. Registering many
16+
// closed-generic types of the same generic abstractions got exponentially slower with the number
17+
// of registrations. These two collections help optimize this.
18+
private Dictionary<Type, ClosedToInstanceProducerProviderDictionaryEntry>? closedProviders;
19+
private List<OpenGenericToInstanceProducerProvider>? openProviders;
20+
1521
internal GenericRegistrationEntry(Container container)
1622
{
1723
this.options = container.Options;
@@ -49,19 +55,55 @@ public void Add(InstanceProducer producer)
4955
{
5056
this.Container.ThrowWhenContainerIsLockedOrDisposed();
5157

52-
this.ThrowWhenConditionalIsRegisteredInOverridingMode(producer);
58+
Type serviceType = producer.ServiceType;
5359

5460
if (!this.AllowOverridingRegistrations)
5561
{
56-
this.ThrowWhenOverlappingRegistrationsExist(producer); // O(n) operation
62+
this.ThrowWhenOverlappingRegistrationsExist(producer);
5763
}
58-
59-
if (this.AllowOverridingRegistrations)
64+
else
6065
{
61-
this.providers.RemoveAll(p => p.ServiceType == producer.ServiceType); // O(n) operation
66+
this.ThrowWhenConditionalIsRegisteredInOverridingMode(producer);
67+
68+
if (this.closedProviders != null)
69+
{
70+
if (this.closedProviders.ContainsKey(serviceType))
71+
{
72+
this.closedProviders.Remove(serviceType);
73+
this.providers.RemoveAll(p => p.ServiceType == serviceType);
74+
}
75+
}
6276
}
6377

64-
this.providers.Add(new ClosedToInstanceProducerProvider(producer));
78+
var provider = new ClosedToInstanceProducerProvider(producer);
79+
80+
this.AddClosedToInstanceProducerProvider(provider);
81+
}
82+
83+
private void AddClosedToInstanceProducerProvider(ClosedToInstanceProducerProvider provider)
84+
{
85+
Type serviceType = provider.ServiceType;
86+
87+
this.providers.Add(provider);
88+
89+
if (this.closedProviders is null)
90+
{
91+
this.closedProviders = new Dictionary<Type, ClosedToInstanceProducerProviderDictionaryEntry>
92+
{
93+
[serviceType] = new ClosedToInstanceProducerProviderDictionaryEntry(provider)
94+
};
95+
}
96+
else
97+
{
98+
if (this.closedProviders.TryGetValue(serviceType, out var entry))
99+
{
100+
entry.Add(provider);
101+
}
102+
else
103+
{
104+
this.closedProviders[serviceType] = new ClosedToInstanceProducerProviderDictionaryEntry(provider);
105+
}
106+
}
65107
}
66108

67109
public void AddGeneric(
@@ -84,10 +126,12 @@ public void AddGeneric(
84126
if (provider.GetAppliesToAllClosedServiceTypes())
85127
{
86128
this.providers.RemoveAll(p => p.GetAppliesToAllClosedServiceTypes());
129+
130+
this.openProviders?.RemoveAll(p => p.GetAppliesToAllClosedServiceTypes());
87131
}
88132
}
89133

90-
this.providers.Add(provider);
134+
this.AddOpenGenericToInstanceProducerProvider(provider);
91135
}
92136

93137
public void Add(
@@ -105,7 +149,19 @@ public void Add(
105149

106150
this.ThrowWhenProviderToRegisterOverlapsWithExistingProvider(provider);
107151

152+
this.AddOpenGenericToInstanceProducerProvider(provider);
153+
}
154+
155+
private void AddOpenGenericToInstanceProducerProvider(OpenGenericToInstanceProducerProvider provider)
156+
{
108157
this.providers.Add(provider);
158+
159+
if (this.openProviders is null)
160+
{
161+
this.openProviders = new List<OpenGenericToInstanceProducerProvider>();
162+
}
163+
164+
this.openProviders.Add(provider);
109165
}
110166

111167
public InstanceProducer? TryGetInstanceProducer(
@@ -172,14 +228,49 @@ private void ThrowWhenOverlappingRegistrationsExist(InstanceProducer producerToR
172228

173229
private IProducerProvider? GetFirstOverlappingProvider(InstanceProducer producerToRegister)
174230
{
231+
ClosedToInstanceProducerProvider? firstOverlappingClosedProvider = null;
232+
OpenGenericToInstanceProducerProvider? firstOverlappingOpenProvider = null;
233+
234+
if (this.closedProviders != null)
235+
{
236+
// PERF: Only closed providers exist. We can speed up the operation by going just through
237+
// the closed providers for the given service type.
238+
if (this.closedProviders.TryGetValue(producerToRegister.ServiceType, out var entry))
239+
{
240+
firstOverlappingClosedProvider = entry.OverlapsWith(producerToRegister);
241+
}
242+
}
243+
244+
if (this.openProviders != null)
245+
{
246+
foreach (var openProvider in this.openProviders)
247+
{
248+
if (openProvider.OverlapsWith(producerToRegister))
249+
{
250+
firstOverlappingOpenProvider = openProvider;
251+
break;
252+
}
253+
}
254+
}
255+
256+
// PERF: Is most cases we can prevent going through the list when there's only one of the two
257+
// overlapping.
258+
if (firstOverlappingClosedProvider is null && firstOverlappingOpenProvider is null) return null;
259+
if (firstOverlappingClosedProvider != null) return firstOverlappingClosedProvider;
260+
if (firstOverlappingOpenProvider != null) return firstOverlappingOpenProvider;
261+
262+
// To bad, there is both an overlapping open and a closed provider. Since we must report the first
263+
// first overlapping provider, we have to go through the list (again).
175264
foreach (var provider in this.providers)
176265
{
177-
if (provider.OverlapsWith(producerToRegister))
266+
if (provider == firstOverlappingClosedProvider ||
267+
provider == firstOverlappingOpenProvider)
178268
{
179269
return provider;
180270
}
181271
}
182272

273+
// Will never come here, but the C# compiler doesn't know.
183274
return null;
184275
}
185276

@@ -541,5 +632,55 @@ private bool IsImplementationApplicableToEveryGenericType(Type implementationTyp
541632
this.ServiceType,
542633
implementationType);
543634
}
635+
636+
private sealed class ClosedToInstanceProducerProviderDictionaryEntry
637+
{
638+
private readonly ClosedToInstanceProducerProvider firstProvider;
639+
640+
private List<ClosedToInstanceProducerProvider>? providers;
641+
642+
public ClosedToInstanceProducerProviderDictionaryEntry(ClosedToInstanceProducerProvider firstProvider)
643+
{
644+
this.firstProvider = firstProvider;
645+
}
646+
647+
public int Count => this.providers?.Count ?? 1;
648+
649+
internal void Add(ClosedToInstanceProducerProvider provider)
650+
{
651+
if (this.providers is null)
652+
{
653+
this.providers = new List<ClosedToInstanceProducerProvider>
654+
{
655+
this.firstProvider,
656+
provider
657+
};
658+
}
659+
else
660+
{
661+
this.providers.Add(provider);
662+
}
663+
}
664+
665+
internal ClosedToInstanceProducerProvider? OverlapsWith(InstanceProducer producerToRegister)
666+
{
667+
if (this.providers is null)
668+
{
669+
return this.firstProvider.OverlapsWith(producerToRegister) ? this.firstProvider : null;
670+
}
671+
else
672+
{
673+
foreach (var provider in this.providers)
674+
{
675+
if (provider.OverlapsWith(producerToRegister))
676+
{
677+
return provider;
678+
}
679+
}
680+
681+
return null;
682+
}
683+
}
684+
}
544685
}
545686
}

0 commit comments

Comments
 (0)