Skip to content
This repository was archived by the owner on Nov 2, 2018. It is now read-only.

Commit bbb9acf

Browse files
committed
Changes to improve performance
- Track disposables in a list with lock instead of a concurrent bag - Allocate a single _createServiceAccessor instead of one per instance - Don't allocate disposable list until used - Only use it for transient objects - Use _resolvedServices as a lockObject to avoid allocating a lockObj
1 parent 42fc918 commit bbb9acf

File tree

3 files changed

+99
-30
lines changed

3 files changed

+99
-30
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using System;
2+
using System.Diagnostics;
3+
4+
namespace System.Collections.Concurrent
5+
{
6+
internal static class ConcurrentDictionaryExtensions
7+
{
8+
// From https://github.com/dotnet/corefx/issues/394#issuecomment-69494764
9+
// This lets us pass a state parameter allocation free GetOrAdd
10+
internal static TValue GetOrAdd<TKey, TValue, TArg>(this ConcurrentDictionary<TKey, TValue> dictionary, TKey key, Func<TKey, TArg, TValue> valueFactory, TArg arg)
11+
{
12+
Debug.Assert(dictionary != null);
13+
Debug.Assert(key != null);
14+
Debug.Assert(valueFactory != null);
15+
16+
while (true)
17+
{
18+
TValue value;
19+
if (dictionary.TryGetValue(key, out value))
20+
{
21+
return value;
22+
}
23+
24+
value = valueFactory(key, arg);
25+
if (dictionary.TryAdd(key, value))
26+
{
27+
return value;
28+
}
29+
}
30+
}
31+
32+
}
33+
}

src/Microsoft.Framework.DependencyInjection/ServiceProvider.cs

Lines changed: 43 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,13 @@ namespace Microsoft.Framework.DependencyInjection
1717
/// </summary>
1818
internal class ServiceProvider : IServiceProvider, IDisposable
1919
{
20-
private readonly object _sync = new object();
21-
22-
private readonly Func<Type, Func<ServiceProvider, object>> _createServiceAccessor;
2320
private readonly ServiceProvider _root;
2421
private readonly ServiceTable _table;
2522

2623
private readonly Dictionary<IService, object> _resolvedServices = new Dictionary<IService, object>();
27-
private ConcurrentBag<IDisposable> _disposables = new ConcurrentBag<IDisposable>();
24+
private List<IDisposable> _transientDisposables;
25+
26+
private static readonly Func<Type, ServiceProvider, Func<ServiceProvider, object>> _createServiceAccessor = CreateServiceAccessor;
2827

2928
public ServiceProvider(IEnumerable<ServiceDescriptor> serviceDescriptors)
3029
{
@@ -34,38 +33,35 @@ public ServiceProvider(IEnumerable<ServiceDescriptor> serviceDescriptors)
3433
_table.Add(typeof(IServiceProvider), new ServiceProviderService());
3534
_table.Add(typeof(IServiceScopeFactory), new ServiceScopeService());
3635
_table.Add(typeof(IEnumerable<>), new OpenIEnumerableService(_table));
37-
38-
// Caching to avoid allocating a Func<,> object per call to GetService
39-
_createServiceAccessor = CreateServiceAccessor;
4036
}
4137

4238
// This constructor is called exclusively to create a child scope from the parent
4339
internal ServiceProvider(ServiceProvider parent)
4440
{
4541
_root = parent._root;
4642
_table = parent._table;
47-
48-
// Caching to avoid allocating a Func<,> object per call to GetService
49-
_createServiceAccessor = CreateServiceAccessor;
5043
}
5144

45+
// Reusing _resolvedServices as an implementation detail of the lock
46+
private object SyncObject => _resolvedServices;
47+
5248
/// <summary>
5349
/// Gets the service object of the specified type.
5450
/// </summary>
5551
/// <param name="serviceType"></param>
5652
/// <returns></returns>
5753
public object GetService(Type serviceType)
5854
{
59-
var realizedService = _table.RealizedServices.GetOrAdd(serviceType, _createServiceAccessor);
55+
var realizedService = _table.RealizedServices.GetOrAdd(serviceType, _createServiceAccessor, this);
6056
return realizedService.Invoke(this);
6157
}
6258

63-
private Func<ServiceProvider, object> CreateServiceAccessor(Type serviceType)
59+
private static Func<ServiceProvider, object> CreateServiceAccessor(Type serviceType, ServiceProvider serviceProvider)
6460
{
65-
var callSite = GetServiceCallSite(serviceType, new HashSet<Type>());
61+
var callSite = serviceProvider.GetServiceCallSite(serviceType, new HashSet<Type>());
6662
if (callSite != null)
6763
{
68-
return RealizeService(_table, serviceType, callSite);
64+
return RealizeService(serviceProvider._table, serviceType, callSite);
6965
}
7066

7167
return _ => null;
@@ -145,14 +141,27 @@ internal IServiceCallSite GetResolveCallSite(IService service, ISet<Type> callSi
145141

146142
public void Dispose()
147143
{
148-
var disposables = Interlocked.Exchange(ref _disposables, null);
149-
150-
if (disposables != null)
144+
lock (SyncObject)
151145
{
152-
foreach (var disposable in disposables)
146+
if (_transientDisposables != null)
147+
{
148+
foreach (var disposable in _transientDisposables)
149+
{
150+
disposable.Dispose();
151+
}
152+
153+
_transientDisposables.Clear();
154+
}
155+
156+
// PERF: We've enumerating the dictionary so that we don't allocate to enumerate.
157+
// .Values allocates a KeyCollection on the heap, enumerating the dictionary allocates
158+
// a struct enumerator
159+
foreach (var entry in _resolvedServices)
153160
{
154-
disposable.Dispose();
161+
(entry.Value as IDisposable)?.Dispose();
155162
}
163+
164+
_resolvedServices.Clear();
156165
}
157166
}
158167

@@ -163,7 +172,15 @@ private object CaptureDisposable(object service)
163172
var disposable = service as IDisposable;
164173
if (disposable != null)
165174
{
166-
_disposables.Add(disposable);
175+
lock (SyncObject)
176+
{
177+
if (_transientDisposables == null)
178+
{
179+
_transientDisposables = new List<IDisposable>();
180+
}
181+
182+
_transientDisposables.Add(disposable);
183+
}
167184
}
168185
}
169186
return service;
@@ -255,11 +272,11 @@ public ScopedCallSite(IService key, IServiceCallSite serviceCallSite)
255272
public virtual object Invoke(ServiceProvider provider)
256273
{
257274
object resolved;
258-
lock (provider._sync)
275+
lock (provider._resolvedServices)
259276
{
260277
if (!provider._resolvedServices.TryGetValue(_key, out resolved))
261278
{
262-
resolved = provider.CaptureDisposable(_serviceCallSite.Invoke(provider));
279+
resolved = _serviceCallSite.Invoke(provider);
263280
provider._resolvedServices.Add(_key, resolved);
264281
}
265282
}
@@ -284,12 +301,8 @@ public virtual Expression Build(Expression providerExpression)
284301
keyExpression,
285302
resolvedExpression);
286303

287-
var captureDisposableExpression = Expression.Assign(
288-
resolvedExpression,
289-
Expression.Call(
290-
providerExpression,
291-
CaptureDisposableMethodInfo,
292-
_serviceCallSite.Build(providerExpression)));
304+
var assignExpression = Expression.Assign(
305+
resolvedExpression, _serviceCallSite.Build(providerExpression));
293306

294307
var addValueExpression = Expression.Call(
295308
resolvedServicesExpression,
@@ -302,7 +315,7 @@ public virtual Expression Build(Expression providerExpression)
302315
new[] { resolvedExpression },
303316
Expression.IfThen(
304317
Expression.Not(tryGetValueExpression),
305-
Expression.Block(captureDisposableExpression, addValueExpression)),
318+
Expression.Block(assignExpression, addValueExpression)),
306319
resolvedExpression);
307320

308321
return Lock(providerExpression, blockExpression);
@@ -312,7 +325,7 @@ private static Expression Lock(Expression providerExpression, Expression body)
312325
{
313326
// The C# compiler would copy the lock object to guard against mutation.
314327
// We don't, since we know the lock object is readonly.
315-
var syncField = Expression.Field(providerExpression, "_sync");
328+
var syncField = Expression.Field(providerExpression, "_resolvedServices");
316329
var lockWasTaken = Expression.Variable(typeof(bool), "lockWasTaken");
317330

318331
var monitorEnter = Expression.Call(MonitorEnterMethodInfo, syncField, lockWasTaken);

test/Microsoft.Framework.DependencyInjection.Tests/ScopingContainerTestBase.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,16 +76,39 @@ public void DisposingScopeDisposesService()
7676
{
7777
var container = CreateContainer();
7878
FakeService disposableService;
79+
FakeService transient1;
80+
FakeService transient2;
81+
FakeService transient3;
82+
FakeService singleton;
83+
84+
transient3 = (FakeService)container.GetService<IFakeService>();
7985

8086
var scopeFactory = container.GetService<IServiceScopeFactory>();
8187
using (var scope = scopeFactory.CreateScope())
8288
{
8389
disposableService = (FakeService)scope.ServiceProvider.GetService<IFakeScopedService>();
90+
transient1 = (FakeService)scope.ServiceProvider.GetService<IFakeService>();
91+
transient2 = (FakeService)scope.ServiceProvider.GetService<IFakeService>();
92+
singleton = (FakeService)scope.ServiceProvider.GetService<IFakeSingletonService>();
8493

8594
Assert.False(disposableService.Disposed);
95+
Assert.False(transient1.Disposed);
96+
Assert.False(transient2.Disposed);
97+
Assert.False(singleton.Disposed);
8698
}
8799

88100
Assert.True(disposableService.Disposed);
101+
Assert.True(transient1.Disposed);
102+
Assert.True(transient2.Disposed);
103+
Assert.False(singleton.Disposed);
104+
105+
var disposableContainer = container as IDisposable;
106+
if (disposableContainer != null)
107+
{
108+
disposableContainer.Dispose();
109+
Assert.True(singleton.Disposed);
110+
Assert.True(transient3.Disposed);
111+
}
89112
}
90113

91114
[Fact]

0 commit comments

Comments
 (0)