Benchmark of different double dispatch methods in C#
// depends on BenchmarkDotNet 0.10.10 | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Reflection; | |
using BenchmarkDotNet.Attributes; | |
using BenchmarkDotNet.Attributes.Jobs; | |
using BenchmarkDotNet.Running; | |
namespace BenchmarkDynamicDispatch | |
{ | |
[ClrJob, CoreJob] | |
public class Program | |
{ | |
static void Main() | |
{ | |
BenchmarkRunner.Run<Program>(); | |
} | |
[Benchmark] | |
public void DynamicDispatch() | |
{ | |
_dynamicDispatch.Dispatch(_event); | |
} | |
[Benchmark] | |
public void UncachedReflectionDispatch() | |
{ | |
_uncachedReflectionDispatch.Dispatch(_event); | |
} | |
[Benchmark] | |
public void CachedReflectionDispatch() | |
{ | |
_cachedReflectionDispatch.Dispatch(_event); | |
} | |
[Benchmark] | |
public void LambdaDispatch() | |
{ | |
_lambdaDispatch.Dispatch(_event); | |
} | |
[Benchmark] | |
public void SwitchDispatch() | |
{ | |
_switchDispatch.Dispatch(_event); | |
} | |
[IterationSetup] | |
public void Initialize() | |
{ | |
_dynamicDispatch = new DynamicDispatch(); | |
_uncachedReflectionDispatch = new UncachedReflectionDispatch(); | |
_cachedReflectionDispatch = new CachedReflectionDispatch(); | |
_lambdaDispatch = new LambdaDispatch(); | |
_switchDispatch = new SwitchDispatch(); | |
} | |
private DynamicDispatch _dynamicDispatch; | |
private UncachedReflectionDispatch _uncachedReflectionDispatch; | |
private CachedReflectionDispatch _cachedReflectionDispatch; | |
private LambdaDispatch _lambdaDispatch; | |
private SwitchDispatch _switchDispatch; | |
private readonly SomeEvent _event = new SomeEvent(); | |
} | |
interface IEvent | |
{ | |
} | |
class SomeEvent: IEvent | |
{ | |
} | |
class AnotherEvent: IEvent | |
{ | |
} | |
class YetAnotherEvent: IEvent | |
{ | |
} | |
class AndAnotherEvent: IEvent | |
{ | |
} | |
abstract class DispatcherBase | |
{ | |
protected void Handle(SomeEvent @event) | |
{ | |
} | |
protected void Handle(AnotherEvent something) | |
{ | |
} | |
protected void Handle(YetAnotherEvent something) | |
{ | |
} | |
protected void Handle(AndAnotherEvent something) | |
{ | |
} | |
protected void Handle(Program something) | |
{ | |
} | |
protected void Handle(List<int> something) | |
{ | |
} | |
protected void Handle(bool something) | |
{ | |
} | |
} | |
class DynamicDispatch: DispatcherBase | |
{ | |
public void Dispatch(IEvent @event) | |
{ | |
Handle((dynamic) @event); | |
} | |
} | |
class UncachedReflectionDispatch: DispatcherBase | |
{ | |
public void Dispatch(IEvent @event) | |
{ | |
var method = typeof(UncachedReflectionDispatch).GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy) | |
.First(m => IsMatch(m, @event.GetType())); | |
method.Invoke(this, new object[] { @event }); | |
} | |
private bool IsMatch(MethodInfo method, Type eventType) | |
{ | |
if(method.Name != nameof(Handle)) | |
return false; | |
var parameters = method.GetParameters(); | |
return parameters.Length == 1 && | |
parameters[0].ParameterType == eventType; | |
} | |
} | |
class CachedReflectionDispatch: DispatcherBase | |
{ | |
public void Dispatch(IEvent @event) | |
{ | |
_handleMethods[@event.GetType()].Invoke(this, new object[] { @event }); | |
} | |
static CachedReflectionDispatch() | |
{ | |
_handleMethods = typeof(CachedReflectionDispatch) | |
.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy) | |
.Where(IsEligibleMethod) | |
.ToDictionary(m => m.GetParameters()[0].ParameterType); | |
} | |
private static bool IsEligibleMethod(MethodInfo method) | |
{ | |
return method.Name == nameof(Handle) && | |
method.GetParameters().Length == 1; | |
} | |
private static readonly Dictionary<Type, MethodInfo> _handleMethods; | |
} | |
class LambdaDispatch: DispatcherBase | |
{ | |
public void Dispatch(IEvent @event) | |
{ | |
_handlers[@event.GetType()](@event); | |
} | |
public LambdaDispatch() | |
{ | |
_handlers.Add(typeof(SomeEvent), e => Handle((SomeEvent) e)); | |
} | |
private readonly Dictionary<Type, Action<IEvent>> _handlers = new Dictionary<Type, Action<IEvent>>(); | |
} | |
class SwitchDispatch: DispatcherBase | |
{ | |
public void Dispatch(IEvent @event) | |
{ | |
switch(@event) | |
{ | |
case YetAnotherEvent x: | |
Handle(x); | |
break; | |
case SomeEvent someEvent: | |
Handle(someEvent); | |
break; | |
case AnotherEvent x: | |
Handle(x); | |
break; | |
case AndAnotherEvent x: | |
Handle(x); | |
break; | |
} | |
} | |
} | |
} | |
// results: | |
// Method | Job | Runtime | Mean | Error | StdDev | | |
//--------------------------- |----- |-------- |-----------:|-----------:|-----------:| | |
// DynamicDispatch | Clr | Clr | 19.340 ns | 0.4259 ns | 0.5971 ns | | |
// UncachedReflectionDispatch | Clr | Clr | 659.277 ns | 11.9960 ns | 11.2211 ns | | |
// CachedReflectionDispatch | Clr | Clr | 226.959 ns | 2.8392 ns | 2.6558 ns | | |
// LambdaDispatch | Clr | Clr | 21.140 ns | 0.4468 ns | 0.6408 ns | | |
// SwitchDispatch | Clr | Clr | 5.438 ns | 0.1333 ns | 0.1247 ns | | |
// DynamicDispatch | Core | Core | 11.537 ns | 0.1668 ns | 0.1560 ns | | |
// UncachedReflectionDispatch | Core | Core | 644.922 ns | 12.8038 ns | 18.3628 ns | | |
// CachedReflectionDispatch | Core | Core | 197.251 ns | 3.9763 ns | 3.9052 ns | | |
// LambdaDispatch | Core | Core | 21.248 ns | 0.4428 ns | 0.4349 ns | | |
// SwitchDispatch | Core | Core | 5.901 ns | 0.1572 ns | 0.1931 ns | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment