From b4059f03b260984442ec92457b1ae7854046530a Mon Sep 17 00:00:00 2001 From: alexmg Date: Sun, 10 Feb 2019 13:54:07 +1000 Subject: [PATCH] Reduced locking in ComponentRegistry to improve concurrency of service resolution. Change extracted from PR #953. Added some benchmarks for concurrent resolution. --- .../Autofac.Benchmarks/ConcurrencyBenchmak.cs | 96 +++++++++++++++++++ bench/Autofac.Benchmarks/Harness.cs | 6 ++ .../Core/Registration/ComponentRegistry.cs | 35 +++++-- 3 files changed, 131 insertions(+), 6 deletions(-) create mode 100644 bench/Autofac.Benchmarks/ConcurrencyBenchmak.cs diff --git a/bench/Autofac.Benchmarks/ConcurrencyBenchmak.cs b/bench/Autofac.Benchmarks/ConcurrencyBenchmak.cs new file mode 100644 index 000000000..051c4256c --- /dev/null +++ b/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 +{ + /// + /// Tests the performance of concurrent resolution of a (reasonably) deeply-nested object graph. + /// + public class ConcurrencyBenchmark + { + private readonly IContainer _container; + + public ConcurrencyBenchmark() + { + var builder = new ContainerBuilder(); + builder.RegisterType().SingleInstance(); + builder.RegisterType(); + builder.RegisterType(); + builder.RegisterType(); + builder.RegisterType(); + builder.RegisterType(); + builder.RegisterType(); + _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(ResolveTaskCount); + + for (var i = 0; i < ResolveTaskCount; i++) + { + var task = Task.Run(() => + { + try + { + for (var j = 0; j < ResolvesPerTask; j++) + { + var instance = _container.Resolve(); + 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 + } +} diff --git a/bench/Autofac.Benchmarks/Harness.cs b/bench/Autofac.Benchmarks/Harness.cs index d656a27a0..e9cd87c24 100644 --- a/bench/Autofac.Benchmarks/Harness.cs +++ b/bench/Autofac.Benchmarks/Harness.cs @@ -42,6 +42,12 @@ public void DeepGraphResolve() BenchmarkRunner.Run(); } + [Fact] + public void Concurrency() + { + BenchmarkRunner.Run(); + } + [Fact] public void Decorator_Keyed_Generic() { diff --git a/src/Autofac/Core/Registration/ComponentRegistry.cs b/src/Autofac/Core/Registration/ComponentRegistry.cs index fe594aeaa..e2696d1d1 100644 --- a/src/Autofac/Core/Registration/ComponentRegistry.cs +++ b/src/Autofac/Core/Registration/ComponentRegistry.cs @@ -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; @@ -65,7 +66,7 @@ public class ComponentRegistry : Disposable, IComponentRegistry /// /// Keeps track of the status of registered services. /// - private readonly Dictionary _serviceInfo = new Dictionary(); + private readonly ConcurrentDictionary _serviceInfo = new ConcurrentDictionary(); private readonly ConcurrentDictionary> _decorators = new ConcurrentDictionary>(); @@ -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); } } @@ -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; @@ -233,9 +242,13 @@ public IEnumerable 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(); } } @@ -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 GetRegistered() { if (Properties.TryGetValue(MetadataKeys.RegisteredPropertyKey, out var registered))