Closed MisinformedDNA closed 1 year ago
I will make a research
Please add another one test like:
[Benchmark]
public Something FastPureDI() => PureDIClass.ResolveSomething();
Just for the big picture :)
Method | Mean | Error | StdDev | Ratio | RatioSD |
---|---|---|---|---|---|
PureDI_ResolveType | 4.561 ns | 0.1527 ns | 0.1634 ns | 1.00 | 0.00 |
PureDI_DirectMethod | 2.370 ns | 0.0657 ns | 0.0583 ns | 0.52 | 0.03 |
MS_ResolveType | 42.272 ns | 0.2464 ns | 0.2184 ns | 9.23 | 0.36 |
MS_ImplementationFactory | 55.598 ns | 0.2616 ns | 0.2447 ns | 12.17 | 0.45 |
PureDI_ResolvedWithMS | 66.791 ns | 0.7181 ns | 0.6717 ns | 14.62 | 0.63 |
CommunityToolkit | 30.811 ns | 0.2624 ns | 0.2454 ns | 6.74 | 0.23 |
PureDI_ResolvedWithCommunityToolkit | 53.565 ns | 0.5580 ns | 0.5219 ns | 11.72 | 0.49 |
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using Microsoft.Extensions.DependencyInjection;
using Pure.DI;
using CommunityToolkit.Mvvm.DependencyInjection;
var summary = BenchmarkRunner.Run<DIComparison>();
//[ShortRunJob]
public class DIComparison
{
private IServiceProvider _microsoftServiceProvider;
private IServiceProvider _pureDIServiceProvider;
private IServiceProvider _handCraftedServiceProvider;
[GlobalSetup]
public void Setup()
{
_microsoftServiceProvider = new ServiceCollection()
.AddTransient<Something>()
.AddTransient<SomethingElse>()
.BuildServiceProvider();
_pureDIServiceProvider = new ServiceCollection()
.AddPureDIClass()
.BuildServiceProvider();
Ioc.Default.ConfigureServices(_microsoftServiceProvider);
_handCraftedServiceProvider = new ServiceCollection()
.AddTransient<Something>(sp => new Something(new SomethingElse()))
.AddTransient<SomethingElse>(sp => new SomethingElse())
.BuildServiceProvider();
}
[GlobalSetup(Target = nameof(PureDI_ResolvedWithCommunityToolkit))]
public void Setup2()
{
_pureDIServiceProvider = new ServiceCollection()
.AddPureDIClass()
.BuildServiceProvider();
Ioc.Default.ConfigureServices(_pureDIServiceProvider);
}
[Benchmark(Baseline = true)]
public Something PureDI_ResolveType() => PureDIClass.Resolve<Something>();
[Benchmark]
public Something PureDI_DirectMethod() => PureDIClass.ResolveSomething();
[Benchmark]
public Something MS_ResolveType() => _microsoftServiceProvider.GetRequiredService<Something>();
[Benchmark]
public Something MS_ImplementationFactory() => _handCraftedServiceProvider.GetRequiredService<Something>();
[Benchmark]
public Something PureDI_ResolvedWithMS() => _pureDIServiceProvider.GetRequiredService<Something>();
[Benchmark]
public Something CommunityToolkit() => Ioc.Default.GetRequiredService<Something>();
[Benchmark]
public Something PureDI_ResolvedWithCommunityToolkit() => Ioc.Default.GetRequiredService<Something>();
}
public class Something
{
public Something(SomethingElse somethingElse) { }
}
public class SomethingElse { }
public static partial class PureDIClass
{
public static void Setup() => DI.Setup()
.Bind<Something>().As(Lifetime.Transient).To<Something>()
.Bind<SomethingElse>().As(Lifetime.Transient).To<SomethingElse>();
}
@MisinformedDNA thank you, I am going to add this benchmarks to Pure.DI.Benchmark. Any objections?
By the way, you can leave out the Transient
because of it is the default lifetime. Also you can override the default lifetime by the call like Default(Lifetime.Singleton)
.
Also you might skip adding binding for SomethingElse
because of it is not abstract type or interface and can be created by constructor. And it is not a composition root type. So a binding is not required there and this dependency will be automatically injected into Something
.
Thus this code:
public static partial class PureDIClass
{
public static void Setup() => DI.Setup()
.Bind<Something>().To<Something>();
}
should work too.
Please see the following samples:
@MisinformedDNA thank you, I am going to add this benchmarks to Pure.DI.Benchmark. Any objections?
Nope. Sounds good.
This issue was fixed and the benchmarks were added. NuGet
@NikolayPianikov
Don't you mean "with Service Collection":
_pureDIWithoutServiceCollectionServiceProvider = new Microsoft.Extensions.DependencyInjection.ServiceCollection()
.AddServiceCollectionDI()
.BuildServiceProvider();
[Benchmark(Description = "Service Provider without ServiceCollection in Pure.DI")]
public ICompositionRoot PureDIWithoutServiceCollectionResolve() =>
_pureDIWithoutServiceCollectionServiceProvider.GetRequiredService<ICompositionRoot>();
If so, the benchmarks you ran still show that using Pure.DI with MS's ServiceCollection makes DI slower.
Yes, fixed in the Last commit. Now it works as "crafted" by performance
You are right, I am plannig to reserch how the toolkit makes it faster. But actually it create "crafted" now.
I reopened this ticket and Will close it after the research
What do you mean by "But actually it create "crafted" now."?
It seems odd to me, but .AddTransient<Something>(sp => new Something())
is just slower than .AddTransient<Something>()
What do you mean by "But actually it create "crafted" now."?
I mean that actual code for "ServiceCollection" looks like:
internal static Microsoft.Extensions.DependencyInjection.IServiceCollection AddServiceCollectionDI(this Microsoft.Extensions.DependencyInjection.IServiceCollection services)
{
Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddTransient<Pure.DI.Benchmark.Model.ICompositionRoot>(services, _ => new Pure.DI.Benchmark.Model.CompositionRoot(SingletonPureDIBenchmarkModelService1.Shared, new Pure.DI.Benchmark.Model.Service2(new Pure.DI.Benchmark.Model.Service3(), new Pure.DI.Benchmark.Model.Service3(), new Pure.DI.Benchmark.Model.Service3(), new Pure.DI.Benchmark.Model.Service3(), new Pure.DI.Benchmark.Model.Service3()), new Pure.DI.Benchmark.Model.Service2(new Pure.DI.Benchmark.Model.Service3(), new Pure.DI.Benchmark.Model.Service3(), new Pure.DI.Benchmark.Model.Service3(), new Pure.DI.Benchmark.Model.Service3(), new Pure.DI.Benchmark.Model.Service3()), new Pure.DI.Benchmark.Model.Service2(new Pure.DI.Benchmark.Model.Service3(), new Pure.DI.Benchmark.Model.Service3(), new Pure.DI.Benchmark.Model.Service3(), new Pure.DI.Benchmark.Model.Service3(), new Pure.DI.Benchmark.Model.Service3()), new Pure.DI.Benchmark.Model.Service3()));
Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddTransient<Pure.DI.Benchmark.Model.IService1>(services, _ => SingletonPureDIBenchmarkModelService1.Shared);
Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddTransient<Pure.DI.Benchmark.Model.IService2>(services, _ => new Pure.DI.Benchmark.Model.Service2(new Pure.DI.Benchmark.Model.Service3(), new Pure.DI.Benchmark.Model.Service3(), new Pure.DI.Benchmark.Model.Service3(), new Pure.DI.Benchmark.Model.Service3(), new Pure.DI.Benchmark.Model.Service3()));
Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddTransient<Pure.DI.Benchmark.Model.IService3>(services, _ => new Pure.DI.Benchmark.Model.Service3());
return services;
}
It seems odd to me, but .AddTransient
(sp => new Something()) is just slower than .AddTransient ()
Yes, you are right. But Pure DI cannot use the AddTransient<Something>()
approach because the object graph built by the MS library is different from the Pure DI object graph, but Pure DI should work consistently in any scenario. For example, when developers use factories, tags, or generics, or when DI selects a constructor and etc. That is why the lambda approach is used, since only in this case Pure DI can control the construction of the object graph.
In terms of transients only, PureDI with ServiceCollection will have the same perf as the crafted code, since they use the same lambdas.
As for community toolkit, it is using the same service provider built by the same service collection, but service resolution is simpler for the toolkit, like not checking if the service provider inherits from ISupportRequiredService. But there must be other differences that I'm not seeing.
Yes, I have looked and it looks strange that it is faster than the direct service provider. It actually makes 2 virtual calls:
IL_0000: call class [Microsoft.Toolkit.Mvvm]Microsoft.Toolkit.Mvvm.DependencyInjection.Ioc [Microsoft.Toolkit.Mvvm]Microsoft.Toolkit.Mvvm.DependencyInjection.Ioc::get_Default()
IL_0005: callvirt instance !!0/*class Pure.DI.Benchmark.Model.ICompositionRoot*/ [Microsoft.Toolkit.Mvvm]Microsoft.Toolkit.Mvvm.DependencyInjection.Ioc::GetRequiredService<class Pure.DI.Benchmark.Model.ICompositionRoot>()
IL_000a: ret
and
IL_0010: ldtoken !!0/*T*/
IL_0015: call class [System.Runtime]System.Type [System.Runtime]System.Type::GetTypeFromHandle(valuetype [System.Runtime]System.RuntimeTypeHandle)
IL_001a: callvirt instance object [System.ComponentModel]System.IServiceProvider::GetService(class [System.Runtime]System.Type)
IL_001f: unbox.any !!0/*T*/
It looks like MS extensions do more checks compared to ToolKit because the method GetService(typeof(ICompositionRoot))
without extensions has the same performance:
BenchmarkDotNet=v0.13.2, OS=Windows 11 (10.0.22000.1098/21H2)
Intel Core i7-10875H CPU 2.30GHz, 1 CPU, 16 logical and 8 physical cores
.NET SDK=6.0.301
[Host] : .NET 6.0.10 (6.0.1022.47605), X64 RyuJIT AVX2
DefaultJob : .NET 6.0.10 (6.0.1022.47605), X64 RyuJIT AVX2
Method | Mean | Error | StdDev | Ratio | RatioSD
-- | -- | -- | -- | -- | --
'MS Service Provider' | 24.06 ns | 0.547 ns | 0.899 ns | 1.00 | 0.00
Crafted | 34.66 ns | 0.745 ns | 1.181 ns | 1.44 | 0.06
'Community Toolkit' | 23.12 ns | 0.446 ns | 0.639 ns | 0.96 | 0.05
Pure.DI | 11.85 ns | 0.309 ns | 0.491 ns | 0.49 | 0.02
'Pure.DI with ServiceCollection' | 33.94 ns | 0.731 ns | 1.260 ns | 1.41 | 0.06
If I use Pure.DI to inject and resolve dependencies, then it is really quick. If I use MS DI to inject and resolve dependencies, then it is faster. If I use inject with Pure.DI and resolve with MS DI, it is really slow.
I would have expected injecting with Pure.DI and resolving with MS DI to be somewhere in the middle, not significantly worse. Do you have any Ideas on what is happening?
Code: