Created
November 24, 2017 10:21
-
-
Save heemskerkerik/0a850919f6467431963f667ae260092f to your computer and use it in GitHub Desktop.
Benchmark of different double dispatch methods in C#
This file contains 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
// 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