Skip to content

Commit

Permalink
Do less work and create less garbage when instantiating components vi…
Browse files Browse the repository at this point in the history
…a reflection. #769.
  • Loading branch information
nblumhardt authored and alexmg committed Jan 3, 2017
1 parent 0c32398 commit fff276e
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 26 deletions.
67 changes: 67 additions & 0 deletions bench/Autofac.Benchmarks/DeepGraphResolveBenchmark.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using BenchmarkDotNet.Attributes;
using System;

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

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

[Benchmark]
public void Resolve()
{
var instance = _container.Resolve<A>();
GC.KeepAlive(instance);
}
}

#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
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,11 @@ public void RootContainerResolve()
{
BenchmarkRunner.Run<RootContainerResolveBenchmark>();
}

[Fact]
public void DeepGraphResolve()
{
BenchmarkRunner.Run<DeepGraphResolveBenchmark>();
}
}
}
66 changes: 40 additions & 26 deletions src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ namespace Autofac.Core.Activators.Reflection
public class ReflectionActivator : InstanceActivator, IInstanceActivator
{
private readonly Type _implementationType;
private readonly IEnumerable<Parameter> _configuredProperties;
private readonly IEnumerable<Parameter> _defaultParameters;
private readonly Parameter[] _configuredProperties;
private readonly Parameter[] _defaultParameters;
private readonly ConstructorInfo[] _availableConstructors;

/// <summary>
Expand All @@ -68,9 +68,9 @@ public ReflectionActivator(
_implementationType = implementationType;
ConstructorFinder = constructorFinder;
ConstructorSelector = constructorSelector;
_configuredProperties = configuredProperties;
_configuredProperties = configuredProperties.ToArray();

_defaultParameters = configuredParameters.Concat(new Parameter[] { new AutowiringParameter(), new DefaultValueParameter() });
_defaultParameters = configuredParameters.Concat(new Parameter[] { new AutowiringParameter(), new DefaultValueParameter() }).ToArray();

_availableConstructors = ConstructorFinder.FindConstructors(_implementationType);
}
Expand Down Expand Up @@ -103,17 +103,7 @@ public object ActivateInstance(IComponentContext context, IEnumerable<Parameter>
if (_availableConstructors.Length == 0)
throw new DependencyResolutionException(string.Format(CultureInfo.CurrentCulture, ReflectionActivatorResources.NoConstructorsAvailable, _implementationType, ConstructorFinder));

var constructorBindings = GetConstructorBindings(
context,
parameters,
_availableConstructors);

var validBindings = constructorBindings
.Where(cb => cb.CanInstantiate)
.ToArray();

if (validBindings.Length == 0)
throw new DependencyResolutionException(GetBindingFailureMessage(constructorBindings));
var validBindings = GetValidConstructorBindings(context, parameters);

var selectedBinding = ConstructorSelector.SelectConstructorBinding(validBindings);

Expand All @@ -124,6 +114,40 @@ public object ActivateInstance(IComponentContext context, IEnumerable<Parameter>
return instance;
}

private ConstructorParameterBinding[] GetValidConstructorBindings(IComponentContext context, IEnumerable<Parameter> parameters)
{
// Most often, there will be no `parameters` and/or no `_defaultParameters`; in both of those cases we can avoid allocating.
var prioritisedParameters = parameters.Any() ?
(_defaultParameters.Length == 0 ? parameters : parameters.Concat(_defaultParameters)) :
_defaultParameters;

var constructorBindings = new ConstructorParameterBinding[_availableConstructors.Length];
for (var i = 0; i < _availableConstructors.Length; ++i)
{
constructorBindings[i] = new ConstructorParameterBinding(_availableConstructors[i], prioritisedParameters, context);
}

// Copy-on-write; 99% of components will have a single constructor that can be instantiated.
var validBindings = constructorBindings;
for (var i = 0; i < constructorBindings.Length; ++i)
{
if (!constructorBindings[i].CanInstantiate)
{
// Further optimisation opportunity here
validBindings = constructorBindings
.Where(cb => cb.CanInstantiate)
.ToArray();

break;
}
}

if (validBindings.Length == 0)
throw new DependencyResolutionException(GetBindingFailureMessage(constructorBindings));

return validBindings;
}

private string GetBindingFailureMessage(IEnumerable<ConstructorParameterBinding> constructorBindings)
{
var reasons = new StringBuilder();
Expand All @@ -142,19 +166,9 @@ private string GetBindingFailureMessage(IEnumerable<ConstructorParameterBinding>
reasons);
}

private IEnumerable<ConstructorParameterBinding> GetConstructorBindings(
IComponentContext context,
IEnumerable<Parameter> parameters,
IEnumerable<ConstructorInfo> constructorInfo)
{
var prioritisedParameters = parameters.Concat(_defaultParameters);

return constructorInfo.Select(ci => new ConstructorParameterBinding(ci, prioritisedParameters, context));
}

private void InjectProperties(object instance, IComponentContext context)
{
if (!_configuredProperties.Any())
if (_configuredProperties.Length == 0)
return;

var actualProperties = instance
Expand Down

0 comments on commit fff276e

Please # to comment.