Skip to content

Instantly share code, notes, and snippets.

@heemskerkerik
Created November 24, 2017 10:21
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save heemskerkerik/0a850919f6467431963f667ae260092f to your computer and use it in GitHub Desktop.
Save heemskerkerik/0a850919f6467431963f667ae260092f to your computer and use it in GitHub Desktop.
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