Skip to content

Instantly share code, notes, and snippets.

@Meigyoku-Thmn
Last active June 17, 2023 15:23
Show Gist options
  • Save Meigyoku-Thmn/8da8e6d94c1e99fd3457f2e90a8f09f9 to your computer and use it in GitHub Desktop.
Save Meigyoku-Thmn/8da8e6d94c1e99fd3457f2e90a8f09f9 to your computer and use it in GitHub Desktop.
Benchmark many ways of reflection in .NET
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);
}
}
}
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 |
@Meigyoku-Thmn
Copy link
Author

Conclusion: Compiled Expression is too good, aside from several limitations. A well-crafted dynamic method can even be faster, but harder to make right.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment