Last active
June 17, 2023 15:23
-
-
Save Meigyoku-Thmn/8da8e6d94c1e99fd3457f2e90a8f09f9 to your computer and use it in GitHub Desktop.
Benchmark many ways of reflection in .NET
This file contains hidden or 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
using BenchmarkDotNet.Attributes; | |
using BenchmarkDotNet.Code; | |
using BenchmarkDotNet.Columns; | |
using BenchmarkDotNet.Configs; | |
using BenchmarkDotNet.Loggers; | |
using BenchmarkDotNet.Running; | |
using BenchmarkDotNet.Validators; | |
using HarmonyLib; | |
using Microsoft.Diagnostics.Runtime; | |
using MonoMod.Utils; | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Linq.Expressions; | |
using System.Reflection; | |
using System.Text; | |
using System.Threading.Tasks; | |
using static HarmonyLib.AccessTools; | |
using static MonoMod.Utils.FastReflectionHelper; | |
namespace LibHarmonyPlayground | |
{ | |
class FooClass | |
{ | |
public int a; | |
int b; | |
public FooClass(int a, int b) | |
{ | |
this.a = a; | |
this.b = b; | |
} | |
public void Run() => Console.WriteLine($"{a}{b}"); | |
public void RunHeavy(int a, int b) | |
{ | |
for (var i = 0; i < 10; i++) | |
a++; | |
} | |
} | |
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
var config = ManualConfig | |
.Create(DefaultConfig.Instance) | |
.WithOptions(ConfigOptions.DisableOptimizationsValidator); | |
BenchmarkRunner.Run<ObjectInstantiation>(config); | |
Console.ReadLine(); | |
BenchmarkRunner.Run<MethodInvocation>(config); | |
Console.ReadLine(); | |
BenchmarkRunner.Run<FieldAccess>(config); | |
Console.ReadLine(); | |
} | |
} | |
public class ObjectInstantiation | |
{ | |
static Random rnd = new Random(); | |
[Benchmark] | |
public void UsingDirect() | |
{ | |
var a = new FooClass(rnd.Next(), rnd.Next()); | |
} | |
[Benchmark] | |
public void UsingActivator() | |
{ | |
var a = Activator.CreateInstance(typeof(FooClass), rnd.Next(), rnd.Next()); | |
} | |
[Benchmark] | |
public void UsingInvoke() | |
{ | |
var a = typeof(FooClass).GetConstructor(new[] { typeof(int), typeof(int) }); | |
var b = a.Invoke(new object[] { rnd.Next(), rnd.Next() }); | |
} | |
static ConstructorInfo constructor = typeof(FooClass).GetConstructor(new[] { typeof(int), typeof(int) }); | |
static Func<int, int, object> MakeCallExpression() | |
{ | |
var param1 = Expression.Parameter(typeof(int), "a"); | |
var param2 = Expression.Parameter(typeof(int), "b"); | |
var constructorExpression = Expression.New(constructor, param1, param2); | |
var lambdaExpression = Expression.Lambda<Func<int, int, object>>(constructorExpression, param1, param2); | |
return lambdaExpression.Compile(); | |
} | |
static Func<int, int, object> createHeadersFunc = MakeCallExpression(); | |
[Benchmark] | |
public void UsingCompiledExpression() | |
{ | |
var a = createHeadersFunc(rnd.Next(), rnd.Next()); | |
} | |
static FastInvoker createFooClass = constructor.GetFastInvoker(); | |
[Benchmark] | |
public void UsingMonoMod() | |
{ | |
var a = createFooClass(null, 400, 500); | |
} | |
} | |
public class MethodInvocation | |
{ | |
static Random rnd = new Random(); | |
static FooClass obj = new FooClass(100, 200); | |
[Benchmark] | |
public void UsingDirect() | |
{ | |
obj.RunHeavy(rnd.Next(), rnd.Next()); | |
} | |
static MethodInfo runHeavy = typeof(FooClass).GetMethod("RunHeavy"); | |
[Benchmark] | |
public void UsingInvoke() | |
{ | |
runHeavy.Invoke(obj, new object[] { rnd.Next(), rnd.Next() }); | |
} | |
[Benchmark] | |
public void UsingDynamic() | |
{ | |
(obj as dynamic).RunHeavy(rnd.Next(), rnd.Next()); | |
} | |
static Action<object, int, int> MakeCallExpression() | |
{ | |
var param0 = Expression.Parameter(typeof(object)); | |
var param1 = Expression.Parameter(typeof(int), "a"); | |
var param2 = Expression.Parameter(typeof(int), "b"); | |
var instance = Expression.Convert(param0, typeof(FooClass)); | |
var callExpression = Expression.Call(instance, runHeavy, param1, param2); | |
var lambdaExpression = Expression.Lambda<Action<object, int, int>>(callExpression, param0, param1, param2); | |
return lambdaExpression.Compile(); | |
} | |
static Action<object, int, int> runHeavyExpression = MakeCallExpression(); | |
[Benchmark] | |
public void UsingCompiledExpression() | |
{ | |
runHeavyExpression(obj, rnd.Next(), rnd.Next()); | |
} | |
static Action<FooClass, int, int> runHeavyDelegate = AccessTools.MethodDelegate<Action<FooClass, int, int>>(runHeavy); | |
[Benchmark] | |
public void UsingHarmony() | |
{ | |
runHeavyDelegate(obj, rnd.Next(), rnd.Next()); | |
} | |
static FastInvoker runHeavyFast = runHeavy.GetFastInvoker(); | |
[Benchmark] | |
public void UsingMonoMod() | |
{ | |
runHeavyFast(obj, rnd.Next(), rnd.Next()); | |
} | |
} | |
public class FieldAccess | |
{ | |
static Random rnd = new Random(); | |
static FooClass obj = new FooClass(100, 200); | |
[Benchmark] | |
public void UsingDirect() | |
{ | |
var b = obj.a; | |
} | |
static FieldInfo aField = typeof(FooClass).GetField("a"); | |
[Benchmark] | |
public void UsingGetValue() | |
{ | |
var b = (int)aField.GetValue(obj); | |
} | |
[Benchmark] | |
public void UsingDynamic() | |
{ | |
var b = (int)(obj as dynamic).a; | |
} | |
static Func<object, int> MakeFieldExpression() | |
{ | |
var param0 = Expression.Parameter(typeof(object)); | |
var instance = Expression.Convert(param0, typeof(FooClass)); | |
var fieldAccess = Expression.Field(instance, aField); | |
var lambdaExpression = Expression.Lambda<Func<object, int>>(fieldAccess, param0); | |
return lambdaExpression.Compile(); | |
} | |
static Func<object, int> MakeFieldRefExpression() | |
{ | |
var param0 = Expression.Parameter(typeof(object)); | |
var instance = Expression.Convert(param0, typeof(FooClass)); | |
var fieldAccess = Expression.Field(instance, aField); | |
var lambdaExpression = Expression.Lambda<Func<object, int>>(fieldAccess, param0); | |
return lambdaExpression.Compile(); | |
} | |
static Func<object, int> aFieldExpression = MakeFieldExpression(); | |
[Benchmark] | |
public void UsingCompiledExpression() | |
{ | |
var b = aFieldExpression(obj); | |
} | |
static FieldRef<object, int> aFieldRef = AccessTools.FieldRefAccess<object, int>(aField); | |
[Benchmark] | |
public void UsingHarmony() | |
{ | |
var b = aFieldRef(obj); | |
} | |
static FastInvoker aFieldFast = aField.GetFastInvoker(); | |
[Benchmark] | |
public void UsingMonoMod() | |
{ | |
var b = (int)aFieldFast(obj); | |
} | |
} | |
} |
This file contains hidden or 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
ObjectInstantiation | |
| Method | Mean | Error | StdDev | | |
|------------------------ |----------:|----------:|----------:| | |
| UsingDirect | 16.39 ns | 0.151 ns | 0.134 ns | | |
| UsingActivator | 547.06 ns | 10.861 ns | 13.736 ns | | |
| UsingInvoke | 379.83 ns | 7.408 ns | 11.750 ns | | |
| UsingCompiledExpression | 20.86 ns | 0.395 ns | 0.439 ns | | |
| UsingMonoModFastInvoker | 19.79 ns | 0.123 ns | 0.103 ns | | |
MethodInvocation | |
| Method | Mean | Error | StdDev | | |
|------------------------ |----------:|---------:|---------:| | |
| UsingDirect | 15.54 ns | 0.289 ns | 0.270 ns | | |
| UsingInvoke | 159.69 ns | 1.247 ns | 0.973 ns | | |
| UsingDynamic | 26.01 ns | 0.460 ns | 0.853 ns | | |
| UsingCompiledExpression | 20.64 ns | 0.264 ns | 0.247 ns | | |
| UsingHarmonyDelegate | 16.73 ns | 0.317 ns | 0.312 ns | | |
| UsingMonoModFastInvoker | 29.11 ns | 0.447 ns | 0.397 ns | | |
FieldReading | |
| Method | Mean | Error | StdDev | | |
|--------------------------- |----------:|----------:|----------:| | |
| UsingDirect | 1.207 ns | 0.0106 ns | 0.0094 ns | | |
| UsingGetValue | 67.048 ns | 0.5065 ns | 0.4490 ns | | |
| UsingDynamic | 9.761 ns | 0.1920 ns | 0.1971 ns | | |
| UsingCompiledExpression | 2.275 ns | 0.0449 ns | 0.0600 ns | | |
| UsingHarmonyFieldRefAccess | 2.892 ns | 0.0320 ns | 0.0299 ns | | |
| UsingMonoModFastInvoker | 8.354 ns | 0.1289 ns | 0.1206 ns | | |
FieldWriting | |
| Method | Mean | Error | StdDev | | |
|--------------------------- |----------:|----------:|----------:| | |
| UsingDirect | 7.784 ns | 0.1463 ns | 0.1369 ns | | |
| UsingSetValue | 72.503 ns | 1.0356 ns | 0.8648 ns | | |
| UsingDynamic | 15.683 ns | 0.1435 ns | 0.1272 ns | | |
| UsingCompiledExpression | 8.783 ns | 0.0809 ns | 0.0717 ns | | |
| UsingHarmonyFieldRefAccess | 9.583 ns | 0.1879 ns | 0.1758 ns | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Conclusion: Compiled Expression is too good, aside from several limitations. A well-crafted dynamic method can even be faster, but harder to make right.