Skip to content

Commit

Permalink
Perf improvements (#147)
Browse files Browse the repository at this point in the history
* Setup basic benchmarking project.

* Avoid unnecessary lock when singleton already constructed

* Avoid unneeded reflection calls when resolving already instantiated singleton as dependency.

* Change .Count() to .Length where possible, avoid repeat calls to GetParameters

* Cache 'best constructor' per type.
Provides significant performance improvement for resolving multi-instance registrations.

* Do not auto-register types that are nested *and* private.

* Apply categories and baselines to benchmarks to improve result readability.

* Cahce generic types when resolving open generics.

* Cache lazy automatic factories (per container) for significant perf improvement when resolving multiple times.
  • Loading branch information
Yortw authored May 28, 2021
1 parent 4433b8c commit f70e466
Show file tree
Hide file tree
Showing 8 changed files with 4,920 additions and 166 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,4 @@ src/.vs/TinyIoC/DesignTimeBuild/.dtbcache.v2
.DS_Store

artifacts/
/.vs
9 changes: 9 additions & 0 deletions TinyIoC.sln
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TinyIoC.AspNetExtensions",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TinyMessenger", "src\TinyMessenger\TinyMessenger.csproj", "{C89AD914-7A99-4BFF-B645-AB787FEFC89A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TinyIoc.Benchmarks", "tests\TinyIoc.Benchmarks\TinyIoc.Benchmarks.csproj", "{D8C074F1-4F9F-4E95-B1AC-4D91D98B34D5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -52,13 +54,20 @@ Global
{C89AD914-7A99-4BFF-B645-AB787FEFC89A}.Release|Any CPU.Build.0 = Release|Any CPU
{C89AD914-7A99-4BFF-B645-AB787FEFC89A}.Source|Any CPU.ActiveCfg = Source|Any CPU
{C89AD914-7A99-4BFF-B645-AB787FEFC89A}.Source|Any CPU.Build.0 = Source|Any CPU
{D8C074F1-4F9F-4E95-B1AC-4D91D98B34D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D8C074F1-4F9F-4E95-B1AC-4D91D98B34D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D8C074F1-4F9F-4E95-B1AC-4D91D98B34D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D8C074F1-4F9F-4E95-B1AC-4D91D98B34D5}.Release|Any CPU.Build.0 = Release|Any CPU
{D8C074F1-4F9F-4E95-B1AC-4D91D98B34D5}.Source|Any CPU.ActiveCfg = Debug|Any CPU
{D8C074F1-4F9F-4E95-B1AC-4D91D98B34D5}.Source|Any CPU.Build.0 = Debug|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{C0F65D37-0DF3-43F6-9338-8FC57453876A} = {234A5083-1F24-45B1-9A50-5318A3EC9DFD}
{F6422C73-C40A-489A-A822-387E3098B2F1} = {234A5083-1F24-45B1-9A50-5318A3EC9DFD}
{D8C074F1-4F9F-4E95-B1AC-4D91D98B34D5} = {234A5083-1F24-45B1-9A50-5318A3EC9DFD}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A25E106A-01ED-4DE8-8278-A216350EE569}
Expand Down
421 changes: 255 additions & 166 deletions src/TinyIoC/TinyIoC.cs

Large diffs are not rendered by default.

42 changes: 42 additions & 0 deletions tests/TinyIoc.Benchmarks/AutoRegisterBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Text;
using BenchmarkDotNet.Attributes;

namespace TinyIoc.Benchmarks
{

#if NET48 //These work on either platform but no point running them twice
[SimpleJob(BenchmarkDotNet.Jobs.RuntimeMoniker.Net48)]
[SimpleJob(BenchmarkDotNet.Jobs.RuntimeMoniker.Net461)]
#endif
#if NETCOREAPP3_1_OR_GREATER // These don't seem to work in a FX app
[SimpleJob(BenchmarkDotNet.Jobs.RuntimeMoniker.NetCoreApp31)]
[RyuJitX86Job]
#endif
#if NET48 //These don't seem to work in a .net core app
[RyuJitX64Job]
[MonoJob]
#endif
[MemoryDiagnoser]
public class AutoRegisterBenchmarks
{

[BenchmarkCategory("AutoResolve"), Benchmark(Baseline = true)]
public TinyIoC.Original.TinyIoCContainer Original_AutoRegister()
{
var retVal = new TinyIoC.Original.TinyIoCContainer();
retVal.AutoRegister();
return retVal;
}

[BenchmarkCategory("AutoResolve"), Benchmark]
public TinyIoC.TinyIoCContainer New_AutoRegister()
{
var retVal = new TinyIoC.TinyIoCContainer();
retVal.AutoRegister();
return retVal;
}

}
}
13 changes: 13 additions & 0 deletions tests/TinyIoc.Benchmarks/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;
using BenchmarkDotNet.Running;

namespace TinyIoc.Benchmarks
{
class Program
{
static void Main(string[] args)
{
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
}
}
}
158 changes: 158 additions & 0 deletions tests/TinyIoc.Benchmarks/ResolveBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
using System;
using System.Collections.Generic;
using System.Text;
using BenchmarkDotNet.Attributes;

namespace TinyIoc.Benchmarks
{

#if NET48 //These work on either platform but no point running them twice
[SimpleJob(BenchmarkDotNet.Jobs.RuntimeMoniker.Net48)]
[SimpleJob(BenchmarkDotNet.Jobs.RuntimeMoniker.Net461)]
#endif
#if NETCOREAPP3_1_OR_GREATER // These don't seem to work in a FX app
[SimpleJob(BenchmarkDotNet.Jobs.RuntimeMoniker.NetCoreApp31)]
[RyuJitX86Job]
#endif
#if NET48 //These don't seem to work in a .net core app
[RyuJitX64Job]
[MonoJob]
#endif
[MemoryDiagnoser]
[GroupBenchmarksBy(BenchmarkDotNet.Configs.BenchmarkLogicalGroupRule.ByCategory)]
//[CategoriesColumn]
public class ResolveBenchmarks
{
private TinyIoC.TinyIoCContainer _NewContainer;
private TinyIoC.Original.TinyIoCContainer _OriginalContainer;

[BenchmarkDotNet.Attributes.GlobalSetup]
public void InitContainer()
{
_NewContainer = new TinyIoC.TinyIoCContainer();
_NewContainer.Register<ISingleton, Singleton>().AsSingleton();
_NewContainer.Register<IParameterlessInstance, ParameterlessInstance>().AsMultiInstance();
_NewContainer.Register<ISingleParameterInstance, SingleParameterInstance>().AsMultiInstance();
_NewContainer.Register(typeof(IOpenGeneric<>), typeof(OpenGenericInstance<>)).AsMultiInstance();

_OriginalContainer = new TinyIoC.Original.TinyIoCContainer();
_OriginalContainer.Register<ISingleton, Singleton>().AsSingleton();
_OriginalContainer.Register<IParameterlessInstance, ParameterlessInstance>().AsMultiInstance();
_OriginalContainer.Register<ISingleParameterInstance, SingleParameterInstance>().AsMultiInstance();
_OriginalContainer.Register(typeof(IOpenGeneric<>), typeof(OpenGenericInstance<>)).AsMultiInstance();
}

[BenchmarkCategory("ResolveSingleton"), Benchmark(Baseline = true)]
public ISingleton Original_Resolve_Singleton()
{
return _OriginalContainer.Resolve<ISingleton>();
}

[BenchmarkCategory("ResolveSingleton"), Benchmark]
public ISingleton New_Resolve_Singleton()
{
return _NewContainer.Resolve<ISingleton>();
}


[BenchmarkCategory("ResolveInstance"), Benchmark(Baseline = true)]
public IParameterlessInstance Original_Resolve_Instance_Without_Dependencies()
{
return _OriginalContainer.Resolve<IParameterlessInstance>();
}

[BenchmarkCategory("ResolveInstance"), Benchmark]
public IParameterlessInstance New_Resolve_Instance_Without_Dependencies()
{
return _NewContainer.Resolve<IParameterlessInstance>();
}


[BenchmarkCategory("ResolveInstanceWithSingletonDependency"), Benchmark(Baseline = true)]
public ISingleParameterInstance Original_Resolve_Instance_With_Singleton_Dependency()
{
return _OriginalContainer.Resolve<ISingleParameterInstance>();
}

[BenchmarkCategory("ResolveInstanceWithSingletonDependency"), Benchmark]
public ISingleParameterInstance New_Resolve_Instance_With_Singleton_Dependency()
{
return _NewContainer.Resolve<ISingleParameterInstance>();
}


[BenchmarkCategory("ResolveOpenGeneric"), Benchmark(Baseline = true)]
public IOpenGeneric<string> Original_Resolve_OpenGeneric()
{
return _OriginalContainer.Resolve<IOpenGeneric<string>>();
}

[BenchmarkCategory("ResolveOpenGeneric"), Benchmark]
public IOpenGeneric<string> New_Resolve_OpenGeneric()
{
return _NewContainer.Resolve<IOpenGeneric<string>>();
}


[BenchmarkCategory("ResolveUnregistered"), Benchmark(Baseline = true)]
public UnregisteredInstance Original_Resolve_Unregistered()
{
return _OriginalContainer.Resolve<UnregisteredInstance>();
}

[BenchmarkCategory("ResolveUnregistered"), Benchmark]
public UnregisteredInstance New_Resolve_Unregistered()
{
return _NewContainer.Resolve<UnregisteredInstance>();
}


[BenchmarkCategory("AutomaticFuncFactory"), Benchmark(Baseline = true)]
public Func<ParameterlessInstance> Original_Resolve_AutomaticFuncFactory()
{
return _OriginalContainer.Resolve<Func<ParameterlessInstance>>();
}

[BenchmarkCategory("AutomaticFuncFactory"), Benchmark]
public Func<ParameterlessInstance> New_Resolve_AutomaticFuncFactory()
{
return _NewContainer.Resolve<Func<ParameterlessInstance>>();
}
}

public interface ISingleton
{

}

public class Singleton : ISingleton
{
public Singleton()
{

}
}

public interface IParameterlessInstance { }

public class ParameterlessInstance : IParameterlessInstance { }

public class UnregisteredInstance { public UnregisteredInstance(ISingleton singleton) {} }

public interface ISingleParameterInstance { }

public class SingleParameterInstance : ISingleParameterInstance
{
private readonly ISingleton _Singleton;
public SingleParameterInstance(ISingleton singleton)
{
_Singleton = singleton;
}
}

public interface IOpenGeneric<T> { T Value { get; set; } }
public class OpenGenericInstance<T> : IOpenGeneric<T>
{
public T Value { get; set; }
}
}
28 changes: 28 additions & 0 deletions tests/TinyIoc.Benchmarks/TinyIoc.Benchmarks.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>netcoreapp3.1;net48;net461</TargetFrameworks>
</PropertyGroup>

<ItemGroup>
<Content Include="bin\Release\net48\BenchmarkDotNet.Artifacts\results\TinyIoc.Benchmarks.ResolveBenchmarks-report.html" />
<Content Include="bin\Release\netcoreapp3.1\BenchmarkDotNet.Artifacts\results\TinyIoc.Benchmarks.ResolveBenchmarks-report.html" />
</ItemGroup>

<ItemGroup>
<None Include="bin\Release\net48\BenchmarkDotNet.Artifacts\results\TinyIoc.Benchmarks.ResolveBenchmarks-report-github.md" />
<None Include="bin\Release\net48\BenchmarkDotNet.Artifacts\results\TinyIoc.Benchmarks.ResolveBenchmarks-report.csv" />
<None Include="bin\Release\netcoreapp3.1\BenchmarkDotNet.Artifacts\results\TinyIoc.Benchmarks.ResolveBenchmarks-report-github.md" />
<None Include="bin\Release\netcoreapp3.1\BenchmarkDotNet.Artifacts\results\TinyIoc.Benchmarks.ResolveBenchmarks-report.csv" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\TinyIoC\TinyIoC.csproj" />
</ItemGroup>

</Project>
Loading

0 comments on commit f70e466

Please sign in to comment.