Skip to content

Instantly share code, notes, and snippets.

@asimmon
Last active October 4, 2021 14:15
Show Gist options
  • Save asimmon/4c6e2eee88aa8af46d7a6993f4cd94ab to your computer and use it in GitHub Desktop.
Save asimmon/4c6e2eee88aa8af46d7a6993f4cd94ab to your computer and use it in GitHub Desktop.
CQRS Mediator handle query performance
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");
}
}
<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>
| 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