Skip to content

Instantly share code, notes, and snippets.

@acaly
Last active December 2, 2020 23:11
Show Gist options
  • Save acaly/0226be49bd34b71409e712af6c5075f5 to your computer and use it in GitHub Desktop.
Save acaly/0226be49bd34b71409e712af6c5075f5 to your computer and use it in GitHub Desktop.
Benchmark property set
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System;
using System.Reflection;
using System.Reflection.Emit;
public class ReflectionTest
{
private delegate void RefAction<T1, T2>(ref T1 p1, T2 p2);
private delegate void RefAction<T1, T2, T3>(ref T1 p1, T2 p2, T3 p3);
private class TargetClass
{
public int Value { get; set; }
}
private struct TargetStruct
{
public int Value { get; set; }
}
private readonly PropertyInfo _classProperty;
private readonly MethodInfo _classMethod;
private readonly Action<TargetClass, int> _classDelegate;
private readonly Func<IntPtr> _classLdftn;
private readonly Action<TargetClass, int, IntPtr> _classCalli;
private readonly Action<TargetClass, int> _classCall;
private readonly IntPtr _classFunctionPtr;
private readonly TargetClass _classTarget = new TargetClass();
private readonly PropertyInfo _structProperty;
private readonly MethodInfo _structMethod;
private readonly RefAction<TargetStruct, int> _structDelegate;
private readonly Func<IntPtr> _structLdftn;
private readonly RefAction<TargetStruct, int, IntPtr> _structCalli;
private readonly RefAction<TargetStruct, int> _structCall;
private readonly IntPtr _structFunctionPtr;
private TargetStruct _structTarget = new TargetStruct();
public ReflectionTest()
{
_classProperty = typeof(TargetClass).GetProperty(nameof(TargetClass.Value));
_classMethod = _classProperty.SetMethod;
_classDelegate = (Action<TargetClass, int>)_classMethod.CreateDelegate(typeof(Action<TargetClass, int>));
_classLdftn = GenLdftn(_classMethod);
_classFunctionPtr = _classLdftn();
_classCalli = GenCalli<Action<TargetClass, int, IntPtr>>(typeof(TargetClass));
_classCall = GenCall<Action<TargetClass, int>>(typeof(TargetClass), _classMethod);
_structProperty = typeof(TargetStruct).GetProperty(nameof(TargetStruct.Value));
_structMethod = _structProperty.SetMethod;
_structDelegate = (RefAction<TargetStruct, int>)_structMethod.CreateDelegate(typeof(RefAction<TargetStruct, int>));
_structLdftn = GenLdftn(_structMethod);
_structFunctionPtr = _structLdftn();
_structCalli = GenCalli<RefAction<TargetStruct, int, IntPtr>>(typeof(TargetStruct).MakeByRefType());
_structCall = GenCall<RefAction<TargetStruct, int>>(typeof(TargetStruct).MakeByRefType(), _structMethod);
}
private static Func<IntPtr> GenLdftn(MethodInfo target)
{
var method = new DynamicMethod($"Ldftn_{target.DeclaringType.Name}", typeof(IntPtr), Type.EmptyTypes);
var il = method.GetILGenerator();
il.Emit(OpCodes.Ldftn, target);
il.Emit(OpCodes.Ret);
return (Func<IntPtr>)method.CreateDelegate(typeof(Func<IntPtr>));
}
private static T GenCalli<T>(Type thisType) where T : Delegate
{
var method = new DynamicMethod($"Calli_{thisType.Name}", null, new[] { thisType, typeof(int), typeof(IntPtr) });
var il = method.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldarg_2);
il.EmitCalli(OpCodes.Calli, CallingConventions.HasThis, null, new[] { typeof(int) }, null);
il.Emit(OpCodes.Ret);
return (T)method.CreateDelegate(typeof(T));
}
private static T GenCall<T>(Type thisType, MethodInfo target) where T : Delegate
{
var method = new DynamicMethod($"Call_{thisType.Name}", null, new[] { thisType, typeof(int) });
var il = method.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Call, target);
il.Emit(OpCodes.Ret);
return (T)method.CreateDelegate(typeof(T));
}
[Benchmark]
public void ClassProperty()
{
_classProperty.SetValue(_classTarget, 100);
}
[Benchmark]
public void ClassMethod()
{
_classMethod.Invoke(_classTarget, new object[] { 200 });
}
[Benchmark]
public void ClassDelegate()
{
_classDelegate.Invoke(_classTarget, 300);
}
[Benchmark]
public void ClassCalli()
{
_classCalli.Invoke(_classTarget, 400, _classFunctionPtr);
}
[Benchmark]
public void ClassCall()
{
_classCall.Invoke(_classTarget, 500);
}
[Benchmark]
public void StructProperty()
{
object boxed = _structTarget;
_structProperty.SetValue(boxed, 100);
_structTarget = (TargetStruct)boxed;
}
[Benchmark]
public void StructMethod()
{
object boxed = _structTarget;
_structMethod.Invoke(boxed, new object[] { 200 });
_structTarget = (TargetStruct)boxed;
}
[Benchmark]
public void StructDelegate()
{
_structDelegate.Invoke(ref _structTarget, 300);
}
[Benchmark]
public void StructCalli()
{
_structCalli.Invoke(ref _structTarget, 400, _structFunctionPtr);
}
[Benchmark]
public void StructCall()
{
_structCall.Invoke(ref _structTarget, 500);
}
public static void Main()
{
BenchmarkRunner.Run<ReflectionTest>();
}
}
/*
*
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.630 (2004/?/20H1)
Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=5.0.100
[Host] : .NET Core 5.0.0 (CoreCLR 5.0.20.51904, CoreFX 5.0.20.51904), X64 RyuJIT [AttachedDebugger]
DefaultJob : .NET Core 5.0.0 (CoreCLR 5.0.20.51904, CoreFX 5.0.20.51904), X64 RyuJIT
| Method | Mean | Error | StdDev |
|--------------- |-----------:|----------:|----------:|
| ClassProperty | 156.530 ns | 3.0402 ns | 4.4562 ns |
| ClassMethod | 145.499 ns | 1.5708 ns | 1.3117 ns |
| ClassDelegate | 1.936 ns | 0.0544 ns | 0.0483 ns |
| ClassCalli | 2.526 ns | 0.0599 ns | 0.0561 ns |
| ClassCall | 2.170 ns | 0.0727 ns | 0.0680 ns |
| StructProperty | 164.876 ns | 3.1983 ns | 4.0448 ns |
| StructMethod | 151.287 ns | 2.9812 ns | 4.2756 ns |
| StructDelegate | 2.196 ns | 0.0660 ns | 0.0618 ns |
| StructCalli | 2.839 ns | 0.0938 ns | 0.1314 ns |
| StructCall | 2.228 ns | 0.0545 ns | 0.0510 ns |
*/
/*
Conclusion: ReflectionDelegate is currently the best way. It is not only the fastest (by a small margin),
but also requires no dynamic code generation.
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment