This is a bit tricky to do but can be done with DynamicMethod in the System.Reflection.Emit namespace. It allows us to emit IL at run time that calls these methods without having to refer to valid, visible identifiers in our code. One of the tricks that this class is able to use is to skip various security and visibility checks which we set via parameters in the constructor. From the example, we need to replace the Utils
class. Here is a rewrite of it using DynamicMethod to create delegates:
internal static class DelegateUtils
{
private static readonly Type UtilsType = Type.GetType("System.Security.Cryptography.Utils");
private static readonly Func<int, SafeHandle> CreateHashDel;
private static readonly Action<SafeHandle, byte[], int, int> HashDataDel;
private static readonly Func<SafeHandle, byte[]> EndHashDel;
static DelegateUtils()
{
CreateHashDel = CreateCreateHashDelegate();
HashDataDel = CreateHashDataDelegate();
EndHashDel = CreateEndHashDelegate();
}
internal static SafeHandle CreateHash(int algid)
{
return CreateHashDel(algid);
}
internal static void HashData(SafeHandle h, byte[] data, int ibStart, int cbSize)
{
HashDataDel(h, data, ibStart, cbSize);
}
internal static byte[] EndHash(SafeHandle h)
{
return EndHashDel(h);
}
private static Func<int, SafeHandle> CreateCreateHashDelegate()
{
var prop = UtilsType.GetProperty("StaticProvHandle", BindingFlags.NonPublic | BindingFlags.Static);
var createHashMethod = UtilsType.GetMethods(BindingFlags.NonPublic | BindingFlags.Static)
.FirstOrDefault(mi => mi.Name == "CreateHash" && mi.GetParameters().Length == 2);
var createHashDyn = new DynamicMethod("CreateHashDyn", typeof(SafeHandle), new[] { typeof(int) }, typeof(object), true);
var ilGen = createHashDyn.GetILGenerator();
ilGen.Emit(OpCodes.Call, prop.GetGetMethod(true));
ilGen.Emit(OpCodes.Ldarg_0);
ilGen.Emit(OpCodes.Call, createHashMethod);
ilGen.Emit(OpCodes.Ret);
var del = (Func<int, SafeHandle>)createHashDyn.CreateDelegate(typeof(Func<int, SafeHandle>));
return del;
}
private static Action<SafeHandle, byte[], int, int> CreateHashDataDelegate()
{
var hashDataMethod = UtilsType.GetMethods(BindingFlags.NonPublic | BindingFlags.Static)
.FirstOrDefault(mi => mi.Name == "HashData" && mi.GetParameters().Length == 4);
var hashDataDyn = new DynamicMethod("HashDataDyn", typeof(void), new[] { typeof(SafeHandle), typeof(byte[]), typeof(int), typeof(int) }, typeof(object), true);
var ilGen = hashDataDyn.GetILGenerator();
ilGen.Emit(OpCodes.Ldarg_0);
ilGen.Emit(OpCodes.Ldarg_1);
ilGen.Emit(OpCodes.Ldarg_2);
ilGen.Emit(OpCodes.Ldarg_3);
ilGen.Emit(OpCodes.Call, hashDataMethod);
ilGen.Emit(OpCodes.Ret);
var del = (Action<SafeHandle, byte[], int, int>)hashDataDyn.CreateDelegate(typeof(Action<SafeHandle, byte[], int, int>));
return del;
}
private static Func<SafeHandle, byte[]> CreateEndHashDelegate()
{
var endHashMethod = UtilsType.GetMethods(BindingFlags.NonPublic | BindingFlags.Static)
.FirstOrDefault(mi => mi.Name == "EndHash" && mi.GetParameters().Length == 1);
var endHashDyn = new DynamicMethod("EndHashDyn", typeof(byte[]), new[] { typeof(SafeHandle) }, typeof(object), true);
var ilGen = endHashDyn.GetILGenerator();
ilGen.Emit(OpCodes.Ldarg_0);
ilGen.Emit(OpCodes.Call, endHashMethod);
ilGen.Emit(OpCodes.Ret);
var del = (Func<SafeHandle, byte[]>)endHashDyn.CreateDelegate(typeof(Func<SafeHandle, byte[]>));
return del;
}
}
Next the question is how much of a speed advantage this gives. It gives you something like a 2-4x increase depending on the size of the data you are hashing. Smaller gets a better speed boost probably because we spent less time doing computations there and more time in between method invocations. Here were the results of a quick benchmark:
BenchmarkDotNet=v0.11.1, OS=Windows 10.0.17134.286 (1803/April2018Update/Redstone4)
Intel Core i5-4200U CPU 1.60GHz (Haswell), 1 CPU, 4 logical and 2 physical cores
Frequency=2240904 Hz, Resolution=446.2485 ns, Timer=TSC
[Host] : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 32bit LegacyJIT-v4.7.3163.0
DefaultJob : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 32bit LegacyJIT-v4.7.3163.0
Method | N | Mean | Error | StdDev |
----------- |------ |----------:|----------:|----------:|
Reflection | 1000 | 16.239 us | 0.1252 us | 0.1046 us |
Delegate | 1000 | 4.329 us | 0.0245 us | 0.0230 us |
Reflection | 10000 | 31.832 us | 0.1599 us | 0.1335 us |
Delegate | 10000 | 19.703 us | 0.1005 us | 0.0940 us |
Note that N is number of bytes being hashed. This is using all the code provided in OPs links to create a MD4 implementation and then calling ComputeHash on that.
Benchmark code:
public class MD4DelegateVsReflection
{
private MD4 md4 = MD4.Create();
private byte[] data;
[Params(1000, 10000)]
public int N;
public void SetupData()
{
data = new byte[N];
new Random(42).NextBytes(data);
}
[GlobalSetup(Target = nameof(Reflection))]
public void ReflectionSetup()
{
MD4.SetReflectionUtils();
SetupData();
}
[GlobalSetup(Target = nameof(Delegate))]
public void DelegateSetup()
{
MD4.SetDelegateUtils();
SetupData();
}
[Benchmark]
public byte[] Reflection() => md4.ComputeHash(data);
[Benchmark]
public byte[] Delegate() => md4.ComputeHash(data);
}