Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bench/Autofac.Benchmarks/BenchmarkSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,6 @@ public static class BenchmarkSet
typeof(MultiConstructorBenchmark),
typeof(LambdaResolveBenchmark),
typeof(RequiredPropertyBenchmark),
typeof(ModuleRegistrationBenchmark),
};
}
58 changes: 58 additions & 0 deletions bench/Autofac.Benchmarks/ModuleRegistrationBenchmark.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright (c) Autofac Project. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

namespace Autofac.Benchmarks;

/// <summary>
/// Measures the cost of building a container that registers many modules which do
/// not override <see cref="Module.AttachToComponentRegistration"/> or
/// <see cref="Module.AttachToRegistrationSource"/>.
/// </summary>
/// <remarks>
/// See https://github.com/autofac/Autofac/issues/1446. Prior to the fix, every module
/// unconditionally subscribed to the registry's <c>Registered</c> and
/// <c>RegistrationSourceAdded</c> events. Each subscription replays all existing
/// registrations, so build time and allocations grew quadratically with the number of
/// modules. Modules that do not override the hooks no longer subscribe, removing the
/// quadratic behavior. Compare against a baseline package version (for example
/// <c>--baseline-version 9.1.0</c>) to see the difference.
/// </remarks>
public class ModuleRegistrationBenchmark
{
[Params(100, 1000)]
public int ModuleCount
{
get; set;
}

[Benchmark]
public void BuildContainerWithModules()
{
var builder = new ContainerBuilder();

for (var i = 0; i < ModuleCount; i++)
{
builder.RegisterModule<NoHookModule>();
}

using var container = builder.Build();
GC.KeepAlive(container);
}

// A module that overrides neither hook - the common case from issue #1446.
private sealed class NoHookModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<Service>().As<IService>();
}

private interface IService
{
}

private sealed class Service : IService
{
}
}
}
28 changes: 28 additions & 0 deletions src/Autofac/Core/InternalReflectionCaches.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@ public InternalReflectionCaches(ReflectionCacheSet set)
Usage = ReflectionCacheUsage.Registration,
});

ModuleOverridesAttachToComponentRegistration = set.GetOrCreateCache(nameof(ModuleOverridesAttachToComponentRegistration), _ => new ReflectionCacheDictionary<Type, bool>
{
Usage = ReflectionCacheUsage.Registration,
});

ModuleOverridesAttachToRegistrationSource = set.GetOrCreateCache(nameof(ModuleOverridesAttachToRegistrationSource), _ => new ReflectionCacheDictionary<Type, bool>
{
Usage = ReflectionCacheUsage.Registration,
});

IsGenericEnumerableInterface = set.GetOrCreateCache<ReflectionCacheDictionary<Type, bool>>(nameof(IsGenericEnumerableInterface));
IsGenericListOrCollectionInterfaceType = set.GetOrCreateCache<ReflectionCacheDictionary<Type, bool>>(nameof(IsGenericListOrCollectionInterfaceType));
IsGenericTypeDefinedBy = set.GetOrCreateCache<ReflectionCacheTupleDictionary<Type, bool>>(nameof(IsGenericTypeDefinedBy));
Expand Down Expand Up @@ -151,4 +161,22 @@ public ReflectionCacheDictionary<Type, bool> ServiceKeyUsageByType
{
get;
}

/// <summary>
/// Gets a cache used by <see cref="Module"/> to determine whether a concrete module type
/// overrides <see cref="Module.AttachToComponentRegistration"/>.
/// </summary>
public ReflectionCacheDictionary<Type, bool> ModuleOverridesAttachToComponentRegistration
{
get;
}

/// <summary>
/// Gets a cache used by <see cref="Module"/> to determine whether a concrete module type
/// overrides <see cref="Module.AttachToRegistrationSource"/>.
/// </summary>
public ReflectionCacheDictionary<Type, bool> ModuleOverridesAttachToRegistrationSource
{
get;
}
}
48 changes: 44 additions & 4 deletions src/Autofac/Module.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,28 @@ private void AttachToRegistrations(IComponentRegistryBuilder componentRegistry)
throw new ArgumentNullException(nameof(componentRegistry));
}

componentRegistry.Registered +=
(sender, e) => AttachToComponentRegistration(e.ComponentRegistryBuilder, e.ComponentRegistration);
var moduleType = GetType();
var cache = ReflectionCacheSet.Shared.Internal.ModuleOverridesAttachToComponentRegistration;

var overrides = cache.GetOrAdd(
moduleType,
static t =>
{
var method = t.GetMethod(
nameof(AttachToComponentRegistration),
BindingFlags.Instance | BindingFlags.NonPublic,
binder: null,
types: new[] { typeof(IComponentRegistryBuilder), typeof(IComponentRegistration) },
modifiers: null);

return method is not null && method.DeclaringType != typeof(Module);
});

if (overrides)
{
componentRegistry.Registered +=
(sender, e) => AttachToComponentRegistration(e.ComponentRegistryBuilder, e.ComponentRegistration);
}
}

private void AttachToSources(IComponentRegistryBuilder componentRegistry)
Expand All @@ -143,7 +163,27 @@ private void AttachToSources(IComponentRegistryBuilder componentRegistry)
throw new ArgumentNullException(nameof(componentRegistry));
}

componentRegistry.RegistrationSourceAdded +=
(sender, e) => AttachToRegistrationSource(e.ComponentRegistry, e.RegistrationSource);
var moduleType = GetType();
var cache = ReflectionCacheSet.Shared.Internal.ModuleOverridesAttachToRegistrationSource;

var overrides = cache.GetOrAdd(
moduleType,
static t =>
{
var method = t.GetMethod(
nameof(AttachToRegistrationSource),
BindingFlags.Instance | BindingFlags.NonPublic,
binder: null,
types: new[] { typeof(IComponentRegistryBuilder), typeof(IRegistrationSource) },
modifiers: null);

return method is not null && method.DeclaringType != typeof(Module);
});

if (overrides)
{
componentRegistry.RegistrationSourceAdded +=
(sender, e) => AttachToRegistrationSource(e.ComponentRegistry, e.RegistrationSource);
}
}
}
Loading
Loading