Last active
October 4, 2021 14:15
-
-
Save asimmon/4c6e2eee88aa8af46d7a6993f4cd94ab to your computer and use it in GitHub Desktop.
CQRS Mediator handle query performance
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Concurrent; | |
using System.Reflection; | |
using System.Threading.Tasks; | |
using BenchmarkDotNet.Attributes; | |
using BenchmarkDotNet.Columns; | |
using BenchmarkDotNet.Configs; | |
using BenchmarkDotNet.Order; | |
using BenchmarkDotNet.Reports; | |
using BenchmarkDotNet.Running; | |
using Microsoft.Extensions.DependencyInjection; | |
public static class Program | |
{ | |
public static void Main() | |
{ | |
var summaryStyle = SummaryStyle.Default.WithRatioStyle(RatioStyle.Trend); | |
var testOrderer = new DefaultOrderer(SummaryOrderPolicy.SlowestToFastest); | |
var benchmarkOptions = DefaultConfig.Instance.WithSummaryStyle(summaryStyle).WithOrderer(testOrderer); | |
var summary = BenchmarkRunner.Run<Benchmarks>(benchmarkOptions); | |
} | |
} | |
public class Monitor | |
{ | |
private static readonly ConcurrentDictionary<Type, Type> QueryHandlerTypesCache = new(); | |
private static readonly ConcurrentDictionary<Type, MethodInfo> HandleAsyncMethodCache = new(); | |
private readonly FooQueryHandlerServiceProvider _services; | |
public Monitor(FooQueryHandlerServiceProvider services) | |
{ | |
this._services = services; | |
} | |
public Task<TResult> MethodInfo_WithoutQueryHandlerTypeCache_WithoutHandleAsyncMethodCache<TResult>(IQuery<TResult> query) | |
{ | |
var queryHandlerType = typeof(IQueryHandler<,>).MakeGenericType(query.GetType(), typeof(TResult)); | |
var handler = this._services.GetRequiredService(queryHandlerType); | |
var method = queryHandlerType.GetMethod(nameof(IQueryHandler<IQuery<TResult>, TResult>.HandleAsync)); | |
return (Task<TResult>)method!.Invoke(handler, new object[] { query }) !; | |
} | |
public Task<TResult> MethodInfo_WithoutQueryHandlerTypeCache_WithHandleAsyncMethodCache<TResult>(IQuery<TResult> query) | |
{ | |
var queryHandlerType = typeof(IQueryHandler<,>).MakeGenericType(query.GetType(), typeof(TResult)); | |
var handler = this._services.GetRequiredService(queryHandlerType); | |
var method = HandleAsyncMethodCache.GetOrAdd(queryHandlerType, x => x.GetMethod(nameof(IQueryHandler<IQuery<TResult>, TResult>.HandleAsync))); | |
return (Task<TResult>)method!.Invoke(handler, new object[] { query }) !; | |
} | |
public Task<TResult> MethodInfo_WithQueryHandlerTypeCache_WithoutHandleAsyncMethodCache<TResult>(IQuery<TResult> query) | |
{ | |
var queryHandlerType = QueryHandlerTypesCache.GetOrAdd(query.GetType(), x => typeof(IQueryHandler<,>).MakeGenericType(x, typeof(TResult))); | |
var handler = this._services.GetRequiredService(queryHandlerType); | |
var method = queryHandlerType.GetMethod(nameof(IQueryHandler<IQuery<TResult>, TResult>.HandleAsync)); | |
return (Task<TResult>)method!.Invoke(handler, new object[] { query }) !; | |
} | |
public Task<TResult> MethodInfo_WithQueryHandlerTypeCache_WithHandleAsyncMethodCache<TResult>(IQuery<TResult> query) | |
{ | |
var queryHandlerType = QueryHandlerTypesCache.GetOrAdd(query.GetType(), x => typeof(IQueryHandler<,>).MakeGenericType(x, typeof(TResult))); | |
var handler = this._services.GetRequiredService(queryHandlerType); | |
var method = HandleAsyncMethodCache.GetOrAdd(queryHandlerType, x => x.GetMethod(nameof(IQueryHandler<IQuery<TResult>, TResult>.HandleAsync))); | |
return (Task<TResult>)method!.Invoke(handler, new object[] { query }) !; | |
} | |
public Task<TResult> Dynamic_WithoutQueryHandlerTypeCache<TResult>(IQuery<TResult> query) | |
{ | |
var queryHandlerType = typeof(IQueryHandler<,>).MakeGenericType(query.GetType(), typeof(TResult)); | |
dynamic handler = this._services.GetRequiredService(queryHandlerType); | |
return handler.HandleAsync((dynamic)query); | |
} | |
public Task<TResult> Dynamic_WithQueryHandlerTypeCache<TResult>(IQuery<TResult> query) | |
{ | |
var queryHandlerType = QueryHandlerTypesCache.GetOrAdd(query.GetType(), x => typeof(IQueryHandler<,>).MakeGenericType(x, typeof(TResult))); | |
dynamic handler = this._services.GetRequiredService(queryHandlerType); | |
return handler.HandleAsync((dynamic)query); | |
} | |
public Task<TResult> GenericsOnly<TQuery, TResult>(TQuery query) where TQuery : IQuery<TResult> | |
{ | |
var handler = this._services.GetRequiredService<IQueryHandler<TQuery, TResult>>(); | |
return handler.HandleAsync(query); | |
} | |
} | |
public class FooQueryHandlerServiceProvider : IServiceProvider | |
{ | |
private readonly FooQueryHandler _queryHandler; | |
public FooQueryHandlerServiceProvider(FooQueryHandler queryHandler) | |
{ | |
this._queryHandler = queryHandler; | |
} | |
public object GetService(Type serviceType) | |
{ | |
return this._queryHandler; | |
} | |
} | |
public class Benchmarks | |
{ | |
private Monitor _monitor; | |
private FooQuery _query; | |
[GlobalSetup] | |
public void GlobalSetup() | |
{ | |
var sp = new FooQueryHandlerServiceProvider(new FooQueryHandler()); | |
this._monitor = new Monitor(sp); | |
this._query = new FooQuery(); | |
} | |
[Benchmark(Baseline = true)] | |
public Task<string> MethodInfo_WithoutQueryHandlerTypeCache_WithoutHandleAsyncMethodCache() | |
=> this._monitor.MethodInfo_WithoutQueryHandlerTypeCache_WithoutHandleAsyncMethodCache(this._query); | |
[Benchmark] | |
public Task<string> MethodInfo_WithoutQueryHandlerTypeCache_WithHandleAsyncMethodCache() | |
=> this._monitor.MethodInfo_WithoutQueryHandlerTypeCache_WithHandleAsyncMethodCache(this._query); | |
[Benchmark] | |
public Task<string> MethodInfo_WithQueryHandlerTypeCache_WithoutHandleAsyncMethodCache() | |
=> this._monitor.MethodInfo_WithQueryHandlerTypeCache_WithoutHandleAsyncMethodCache(this._query); | |
[Benchmark] | |
public Task<string> MethodInfo_WithQueryHandlerTypeCache_WithHandleAsyncMethodCache() | |
=> this._monitor.MethodInfo_WithQueryHandlerTypeCache_WithHandleAsyncMethodCache(this._query); | |
[Benchmark] | |
public Task<string> Dynamic_WithoutQueryHandlerTypeCache() | |
=> this._monitor.Dynamic_WithoutQueryHandlerTypeCache(this._query); | |
[Benchmark] | |
public Task<string> Dynamic_WithQueryHandlerTypeCache() | |
=> this._monitor.Dynamic_WithQueryHandlerTypeCache(this._query); | |
[Benchmark] | |
public Task<string> GenericsOnly() | |
=> this._monitor.GenericsOnly<FooQuery, string>(this._query); | |
} | |
public interface IQuery<TResult> | |
{ | |
} | |
public class FooQuery : IQuery<string> | |
{ | |
} | |
public interface IQueryHandler<in TQuery, TResult> where TQuery : IQuery<TResult> | |
{ | |
public Task<TResult> HandleAsync(TQuery query); | |
} | |
public class FooQueryHandler : IQueryHandler<FooQuery, string> | |
{ | |
public Task<string> HandleAsync(FooQuery query) | |
{ | |
return Task.FromResult("bar"); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<Project Sdk="Microsoft.NET.Sdk"> | |
<PropertyGroup> | |
<OutputType>Exe</OutputType> | |
<TargetFramework>net5.0</TargetFramework> | |
</PropertyGroup> | |
<ItemGroup> | |
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" /> | |
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="5.0.0" /> | |
</ItemGroup> | |
</Project> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Method | Mean | Error | StdDev | Ratio | RatioSD | | |
|---------------------------------------------------------------------- |----------:|----------:|---------:|--------------:|--------:| | |
| MethodInfo_WithoutQueryHandlerTypeCache_WithoutHandleAsyncMethodCache | 616.55 ns | 8.706 ns | 8.143 ns | baseline | | | |
| MethodInfo_WithoutQueryHandlerTypeCache_WithHandleAsyncMethodCache | 595.71 ns | 10.246 ns | 9.584 ns | 1.04x faster | 0.01x | | |
| Dynamic_WithoutQueryHandlerTypeCache | 353.55 ns | 5.145 ns | 4.561 ns | 1.75x faster | 0.03x | | |
| MethodInfo_WithQueryHandlerTypeCache_WithoutHandleAsyncMethodCache | 302.91 ns | 3.220 ns | 3.012 ns | 2.04x faster | 0.04x | | |
| MethodInfo_WithQueryHandlerTypeCache_WithHandleAsyncMethodCache | 274.89 ns | 4.586 ns | 4.065 ns | 2.25x faster | 0.04x | | |
| Dynamic_WithQueryHandlerTypeCache | 51.58 ns | 0.572 ns | 0.477 ns | 11.97x faster | 0.22x | | |
| GenericsOnly | 19.95 ns | 0.444 ns | 0.393 ns | 30.94x faster | 0.50x | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment