Skip to content

Instantly share code, notes, and snippets.

@joncloud
Created January 15, 2021 02:43
Show Gist options
  • Save joncloud/a7a554f9d3315634b0851f44716d450e to your computer and use it in GitHub Desktop.
Save joncloud/a7a554f9d3315634b0851f44716d450e to your computer and use it in GitHub Desktop.
Dynamic Method Invocation
BenchmarkDotNet=v0.12.1, OS=ubuntu 20.04
Intel Core i5-2400 CPU 3.10GHz (Sandy Bridge), 1 CPU, 4 logical and 4 physical cores
.NET Core SDK=5.0.101
  [Host]     : .NET Core 5.0.1 (CoreCLR 5.0.120.57516, CoreFX 5.0.120.57516), X64 RyuJIT
  DefaultJob : .NET Core 5.0.1 (CoreCLR 5.0.120.57516, CoreFX 5.0.120.57516), X64 RyuJIT

Method Mean Error StdDev Ratio RatioSD Gen 0 Gen 1 Gen 2 Allocated
Calculator 5.452 ns 0.0532 ns 0.0498 ns 1.00 0.00 0.0076 - - 24 B
Switch 13.419 ns 0.0714 ns 0.0668 ns 2.46 0.03 0.0153 - - 48 B
Reflection 13.642 ns 0.0993 ns 0.0830 ns 2.50 0.03 0.0153 - - 48 B
CachedReflection 724.050 ns 2.9458 ns 2.6114 ns 132.97 1.43 0.2928 - - 920 B
Delegate 14,984.465 ns 32.3751 ns 30.2837 ns 2,748.78 28.21 1.0529 - - 3328 B
GenericDelegateAdd 29,966.604 ns 76.9660 ns 68.2283 ns 5,503.03 44.72 1.9531 - - 6156 B
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
class Program
{
static void Main(string[] args)
{
BenchmarkRunner.Run<CtorTests>();
BenchmarkRunner.Run<RunTests>();
}
}
[MemoryDiagnoser]
public class CtorTests {
[Benchmark(Baseline = true)]
public Calculator Calculator() {
return new Calculator();
}
[Benchmark]
public IMethodInvoker Switch() {
var instance = new Calculator();
return new SwitchMethodInvoker(instance);
}
[Benchmark]
public IMethodInvoker Reflection() {
var instance = new Calculator();
return new ReflectionMethodInvoker(instance);
}
[Benchmark]
public object CachedReflection() {
var instance = new Calculator();
return new CachedReflectionMethodInvoker(instance);
}
[Benchmark]
public object Delegate() {
var instance = new Calculator();
return new DelegateMethodInvoker(instance);
}
[Benchmark]
public object GenericDelegateAdd() {
var instance = new Calculator();
return new GenericDelegateMethodInvoker(instance);
}
}
[MemoryDiagnoser]
public class RunTests {
static Calculator _instance;
static SwitchMethodInvoker _calculator;
static ReflectionMethodInvoker _reflection;
static CachedReflectionMethodInvoker _cachedReflection;
static DelegateMethodInvoker _delegate;
static GenericDelegateMethodInvoker _genericDelegate;
[GlobalSetup]
public static void GlobalSetup() {
_instance = new Calculator();
_calculator = new SwitchMethodInvoker(_instance);
_reflection = new ReflectionMethodInvoker(_instance);
_cachedReflection = new CachedReflectionMethodInvoker(_instance);
_delegate = new DelegateMethodInvoker(_instance);
_genericDelegate = new GenericDelegateMethodInvoker(_instance);
}
[Benchmark(Baseline = true)]
public object Add() {
return _instance.Add(1, 2);
}
[Benchmark]
public object SwitchAdd() {
var args = new object[] { 1, 2 };
return _calculator.Invoke("Add", args);
}
[Benchmark]
public object ReflectionAdd() {
var args = new object[] { 1, 2 };
return _reflection.Invoke(nameof(Calculator.Add), args);
}
[Benchmark]
public object CachedReflectionAdd() {
var args = new object[] { 1, 2 };
return _cachedReflection.Invoke(nameof(Calculator.Add), args);
}
[Benchmark]
public object DelegateAdd() {
var args = new object[] { 1, 2 };
return _delegate.Invoke(nameof(Calculator.Add), args);
}
[Benchmark]
public object GenericDelegateAdd() {
var args = new object[] { 1, 2 };
return _genericDelegate.Invoke(nameof(Calculator.Add), args);
}
}
public interface IMethodInvoker {
object Invoke(string name, object[] args);
}
public class SwitchMethodInvoker : IMethodInvoker {
readonly Calculator _instance;
public SwitchMethodInvoker(Calculator instance) =>
_instance = instance;
public object Invoke(string name, object[] args)
{
return name switch {
"Subtract" => _instance.Subtract((int)args[0], (int)args[1]),
"Add" => _instance.Add((int)args[0], (int)args[1]),
_ => throw new ArgumentOutOfRangeException(nameof(name)),
};
}
}
public class ReflectionMethodInvoker : IMethodInvoker
{
readonly object _instance;
public ReflectionMethodInvoker(object instance) {
_instance = instance;
}
public object Invoke(string name, object[] args)
{
return _instance.GetType().GetMethod(name).Invoke(_instance, args);
}
}
public class CachedReflectionMethodInvoker : IMethodInvoker {
readonly object _instance;
readonly ILookup<string, MethodInfo> _methods;
public CachedReflectionMethodInvoker(object instance) {
_instance = instance;
_methods = _instance.GetType().GetMethods().ToLookup(x => x.Name);
}
public object Invoke(string name, object[] args)
{
return _methods[name].First().Invoke(_instance, args);
}
}
public class DelegateMethodInvoker : IMethodInvoker {
readonly ILookup<string, Delegate> _delegates;
static readonly Dictionary<int, Type> _funcs = typeof(Func<>).Assembly
.GetTypes()
.Where(x => x.IsGenericType)
.Where(x => x.Name.StartsWith("Func`"))
.ToDictionary(x => x.GetGenericArguments().Length);
public DelegateMethodInvoker(object instance) {
_delegates = instance.GetType()
.GetMethods()
.ToLookup(
x => x.Name,
x => {
var types = new List<Type>();
types.AddRange(x.GetParameters().Select(y => y.ParameterType));
types.Add(x.ReturnType);
var delegateType = _funcs[types.Count].MakeGenericType(types.ToArray());
return x.CreateDelegate(delegateType, instance);
}
);
}
public object Invoke(string name, object[] args)
{
return _delegates[name].First().DynamicInvoke(args);
}
}
public class GenericDelegateMethodInvoker : IMethodInvoker
{
readonly Dictionary<string, IInvoker> _invokers;
static readonly Dictionary<int, Type> _invokerTypes = new Dictionary<int, Type> {
[0] = typeof(Invoker<,>),
[1] = typeof(Invoker<,,>),
[2] = typeof(Invoker<,,,>),
};
public GenericDelegateMethodInvoker(object instance) {
_invokers = instance.GetType().GetMethods()
.ToDictionary(
method => method.Name,
method => {
var types = new List<Type>();
types.Add(method.DeclaringType);
var paramTypes = method.GetParameters().Select(x => x.ParameterType).ToList();
var invokerType = _invokerTypes[paramTypes.Count];
types.AddRange(paramTypes);
types.Add(method.ReturnType);
return (IInvoker)Activator.CreateInstance(
invokerType.MakeGenericType(types.ToArray()),
new object[] { instance, method }
);
}
);
}
public object Invoke(string name, object[] args)
{
return _invokers[name].Invoke(args);
}
interface IInvoker {
object Invoke(object[] args);
}
class Invoker<TInstance, TResult> : IInvoker {
readonly Func<TResult> _fn;
public Invoker(TInstance instance, MethodInfo method) {
_fn = method.CreateDelegate<Func<TResult>>(instance);
}
public object Invoke(object[] args) {
return _fn();
}
}
class Invoker<TInstance, TArg, TResult> : IInvoker {
readonly Func<TArg, TResult> _fn;
public Invoker(TInstance instance, MethodInfo method) {
_fn = method.CreateDelegate<Func<TArg, TResult>>(instance);
}
public object Invoke(object[] args) {
return _fn((TArg)args[0]);
}
}
class Invoker<TInstance, TArg1, TArg2, TResult> : IInvoker {
readonly Func<TArg1, TArg2, TResult> _fn;
public Invoker(TInstance instance, MethodInfo method) {
_fn = method.CreateDelegate<Func<TArg1, TArg2, TResult>>(instance);
}
public object Invoke(object[] args) {
return _fn((TArg1)args[0], (TArg2)args[1]);
}
}
}
public class Calculator {
public int Add(int x, int y) {
return x + y;
}
public int Subtract(int x, int y) {
return x - y;
}
}
BenchmarkDotNet=v0.12.1, OS=ubuntu 20.04
Intel Core i5-2400 CPU 3.10GHz (Sandy Bridge), 1 CPU, 4 logical and 4 physical cores
.NET Core SDK=5.0.101
  [Host]     : .NET Core 5.0.1 (CoreCLR 5.0.120.57516, CoreFX 5.0.120.57516), X64 RyuJIT
  DefaultJob : .NET Core 5.0.1 (CoreCLR 5.0.120.57516, CoreFX 5.0.120.57516), X64 RyuJIT

Method Mean Error StdDev Ratio RatioSD Gen 0 Gen 1 Gen 2 Allocated
Add 7.225 ns 0.0387 ns 0.0323 ns 1.00 0.00 0.0076 - - 24 B
SwitchAdd 39.575 ns 0.2392 ns 0.1867 ns 5.48 0.03 0.0357 - - 112 B
ReflectionAdd 368.808 ns 1.2493 ns 1.1686 ns 51.07 0.25 0.0482 - - 152 B
CachedReflectionAdd 382.736 ns 1.9442 ns 1.7235 ns 52.99 0.38 0.0482 - - 152 B
DelegateAdd 577.890 ns 2.0178 ns 1.7887 ns 79.97 0.45 0.0477 - - 152 B
GenericDelegateAdd 57.378 ns 0.2257 ns 0.2001 ns 7.94 0.03 0.0356 - - 112 B
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment