Skip to content

Commit

Permalink
Reduced locking in ComponentRegistry to improve concurrency of servic…
Browse files Browse the repository at this point in the history
…e resolution. Change extracted from PR #953. Added some benchmarks for concurrent resolution.
  • Loading branch information
alexmg committed Feb 10, 2019
1 parent acb742d commit b4059f0
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 6 deletions.
96 changes: 96 additions & 0 deletions bench/Autofac.Benchmarks/ConcurrencyBenchmak.cs
@@ -0,0 +1,96 @@
using BenchmarkDotNet.Attributes;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Xunit;

namespace Autofac.Benchmarks
{
/// <summary>
/// Tests the performance of concurrent resolution of a (reasonably) deeply-nested object graph.
/// </summary>
public class ConcurrencyBenchmark
{
private readonly IContainer _container;

public ConcurrencyBenchmark()
{
var builder = new ContainerBuilder();
builder.RegisterType<A>().SingleInstance();
builder.RegisterType<B1>();
builder.RegisterType<B2>();
builder.RegisterType<C1>();
builder.RegisterType<C2>();
builder.RegisterType<D1>();
builder.RegisterType<D2>();
_container = builder.Build();
}

[Params(10, 100, 1_000)]
public int ResolveTaskCount { get; set; }

[Params(1_000, 10_000)]
public int ResolvesPerTask { get; set; }

[Benchmark]
public void MultipleResolvesOnMultipleTasks()
{
var tasks = new List<Task>(ResolveTaskCount);

for (var i = 0; i < ResolveTaskCount; i++)
{
var task = Task.Run(() =>
{
try
{
for (var j = 0; j < ResolvesPerTask; j++)
{
var instance = _container.Resolve<A>();
Assert.NotNull(instance);
}
}
catch (Exception ex)
{
Assert.True(false, ex.ToString());
}
});
tasks.Add(task);
}

Task.WhenAll(tasks);
}

#pragma warning disable SA1402, SA1502

internal class A
{
public A(B1 b1, B2 b2) { }
}

internal class B1
{
public B1(B2 b2, C1 c1, C2 c2) { }
}

internal class B2
{
public B2(C1 c1, C2 c2) { }
}

internal class C1
{
public C1(C2 c2, D1 d1, D2 d2) { }
}

internal class C2
{
public C2(D1 d1, D2 d2) { }
}

internal class D1 { }

internal class D2 { }

#pragma warning restore SA1402, SA1502
}
}
6 changes: 6 additions & 0 deletions bench/Autofac.Benchmarks/Harness.cs
Expand Up @@ -42,6 +42,12 @@ public void DeepGraphResolve()
BenchmarkRunner.Run<DeepGraphResolveBenchmark>();
}

[Fact]
public void Concurrency()
{
BenchmarkRunner.Run<ConcurrencyBenchmark>();
}

[Fact]
public void Decorator_Keyed_Generic()
{
Expand Down
35 changes: 29 additions & 6 deletions src/Autofac/Core/Registration/ComponentRegistry.cs
Expand Up @@ -29,6 +29,7 @@
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;
using Autofac.Builder;
using Autofac.Features.Decorators;
using Autofac.Util;
Expand Down Expand Up @@ -65,7 +66,7 @@ public class ComponentRegistry : Disposable, IComponentRegistry
/// <summary>
/// Keeps track of the status of registered services.
/// </summary>
private readonly Dictionary<Service, ServiceRegistrationInfo> _serviceInfo = new Dictionary<Service, ServiceRegistrationInfo>();
private readonly ConcurrentDictionary<Service, ServiceRegistrationInfo> _serviceInfo = new ConcurrentDictionary<Service, ServiceRegistrationInfo>();

private readonly ConcurrentDictionary<IComponentRegistration, IEnumerable<IComponentRegistration>> _decorators
= new ConcurrentDictionary<IComponentRegistration, IEnumerable<IComponentRegistration>>();
Expand Down Expand Up @@ -118,9 +119,13 @@ public bool TryGetRegistration(Service service, out IComponentRegistration regis
{
if (service == null) throw new ArgumentNullException(nameof(service));

var info = GetInitializedServiceInfoOrDefault(service);
if (info != null && info.TryGetRegistration(out registration))
return true;

lock (_synchRoot)
{
var info = GetInitializedServiceInfo(service);
info = GetInitializedServiceInfo(service);
return info.TryGetRegistration(out registration);
}
}
Expand All @@ -134,6 +139,10 @@ public bool IsRegistered(Service service)
{
if (service == null) throw new ArgumentNullException(nameof(service));

var info = GetInitializedServiceInfoOrDefault(service);
if (info != null && info.IsRegistered)
return true;

lock (_synchRoot)
{
return GetInitializedServiceInfo(service).IsRegistered;
Expand Down Expand Up @@ -233,9 +242,13 @@ public IEnumerable<IComponentRegistration> RegistrationsFor(Service service)
{
if (service == null) throw new ArgumentNullException(nameof(service));

var info = GetInitializedServiceInfoOrDefault(service);
if (info != null)
return info.Implementations.ToArray();

lock (_synchRoot)
{
var info = GetInitializedServiceInfo(service);
info = GetInitializedServiceInfo(service);
return info.Implementations.ToArray();
}
}
Expand Down Expand Up @@ -366,17 +379,27 @@ private ServiceRegistrationInfo GetInitializedServiceInfo(Service service)
return info;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ServiceRegistrationInfo GetServiceInfo(Service service)
{
ServiceRegistrationInfo existing;
if (_serviceInfo.TryGetValue(service, out existing))
if (_serviceInfo.TryGetValue(service, out var existing))
return existing;

var info = new ServiceRegistrationInfo(service);
_serviceInfo.Add(service, info);
_serviceInfo.TryAdd(service, info);
return info;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ServiceRegistrationInfo GetInitializedServiceInfoOrDefault(Service service)
{
if (_serviceInfo.TryGetValue(service, out var existing) && existing.IsInitialized)
return existing;

return null;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private EventHandler<ComponentRegisteredEventArgs> GetRegistered()
{
if (Properties.TryGetValue(MetadataKeys.RegisteredPropertyKey, out var registered))
Expand Down

0 comments on commit b4059f0

Please sign in to comment.