Skip to content

Instantly share code, notes, and snippets.

@reejk
Created June 8, 2018 10:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save reejk/0a69fa4c40a2d04a216f65e320c0a8bc to your computer and use it in GitHub Desktop.
Save reejk/0a69fa4c40a2d04a216f65e320c0a8bc to your computer and use it in GitHub Desktop.
CodeGeneration Benchmark
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Attributes.Jobs;
namespace Benchmark
{
[ClrJob]
public class CodeGeneration
{
private struct OpCodeHelper
{
public readonly OpCode OpCode;
public readonly int Value;
public OpCodeHelper(OpCode opCode, int value)
{
OpCode = opCode;
Value = value;
}
public void Emit(ILGenerator il)
{
if (OpCode == OpCodes.Ldc_I4)
il.Emit(OpCode, Value);
else
il.Emit(OpCode);
}
public static implicit operator OpCodeHelper(OpCode opCode) => new OpCodeHelper(opCode, 0);
public static implicit operator OpCodeHelper((OpCode, int) opCode) => new OpCodeHelper(opCode.Item1, opCode.Item2);
}
private readonly Func<string, byte> _nativeDelegate;
private readonly Func<string, byte> _expression;
private readonly Func<string, byte> _emitExpressionRunAndSave;
private readonly Func<string, byte> _emitExpressionRunAndCollect;
private readonly Func<string, byte> _emitExpressionRun;
private readonly Func<string, byte> _emitNativeRunAndSave;
private readonly Func<string, byte> _emitNativeRunAndCollect;
private readonly Func<string, byte> _emitNativeRun;
public CodeGeneration()
{
_nativeDelegate = NativeStatic;
_expression = CreateExpression().Compile();
_emitExpressionRunAndSave = EmitExpression(AssemblyBuilderAccess.RunAndSave);
_emitExpressionRunAndCollect = EmitExpression(AssemblyBuilderAccess.RunAndCollect);
_emitExpressionRun = EmitExpression(AssemblyBuilderAccess.Run);
_emitNativeRunAndSave = EmitNative(AssemblyBuilderAccess.RunAndSave);
_emitNativeRunAndCollect = EmitNative(AssemblyBuilderAccess.RunAndCollect);
_emitNativeRun = EmitNative(AssemblyBuilderAccess.Run);
}
private static Func<string, byte> EmitExpression(AssemblyBuilderAccess access)
{
var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("Benchmark.Dynamic." + access), access);
var module = access == AssemblyBuilderAccess.RunAndSave
? assembly.DefineDynamicModule("Benchmark.Dynamic", "Benchmark.Dynamic.dll")
: assembly.DefineDynamicModule("Benchmark.Dynamic");
var typeBuilder = module.DefineType("Test", TypeAttributes.Sealed | TypeAttributes.Public);
var methodBuilder = typeBuilder.DefineMethod("Test", MethodAttributes.Public | MethodAttributes.Static, typeof(byte), new[] { typeof(string) });
CreateExpression().CompileToMethod(methodBuilder);
var type = typeBuilder.CreateType();
if (access == AssemblyBuilderAccess.RunAndSave)
assembly.Save("Benchmark.Dynamic.dll");
var method = type.GetMethod("Test", BindingFlags.Static | BindingFlags.Public);
return (Func<string, byte>)method.CreateDelegate(typeof(Func<string, byte>));
}
/// Emits the same IL code as Visual Studio 2017 for NativeStatic
private static Func<string, byte> EmitNative(AssemblyBuilderAccess access)
{
var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("Benchmark.Dynamic.Native." + access), access);
var module = access == AssemblyBuilderAccess.RunAndSave
? assembly.DefineDynamicModule("Benchmark.Dynamic.Native", "Benchmark.Dynamic.Native.dll")
: assembly.DefineDynamicModule("Benchmark.Dynamic.Native");
var typeBuilder = module.DefineType("Test", TypeAttributes.Sealed | TypeAttributes.Public);
var methodBuilder = typeBuilder.DefineMethod("Test", MethodAttributes.Public | MethodAttributes.Static, typeof(byte), new[] { typeof(string) });
{
var il = methodBuilder.GetILGenerator();
void Emit(params OpCodeHelper[] opCodes) { foreach (var h in opCodes) h.Emit(il); }
var len = il.DeclareLocal(typeof(int));
var ret = il.DeclareLocal(typeof(byte));
var i = il.DeclareLocal(typeof(int));
var ch = il.DeclareLocal(typeof(char));
if (len.LocalIndex != 0 || ret.LocalIndex != 1 || i.LocalIndex != 2 || ch.LocalIndex != 3)
throw new NotSupportedException();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Callvirt, typeof(string).GetProperty(nameof(string.Length)).GetMethod);
il.Emit(OpCodes.Stloc_0);
Emit(OpCodes.Ldloc_0, (OpCodes.Ldc_I4, 255), OpCodes.And, OpCodes.Conv_U1, OpCodes.Stloc_1);
Emit(OpCodes.Ldloc_1, OpCodes.Ldloc_0, OpCodes.Ldc_I4_8, OpCodes.Shr, (OpCodes.Ldc_I4, 255), OpCodes.And, OpCodes.Conv_U1, OpCodes.Xor, OpCodes.Conv_U1, OpCodes.Stloc_1);
Emit(OpCodes.Ldloc_1, OpCodes.Ldloc_0, (OpCodes.Ldc_I4, 16), OpCodes.Shr, (OpCodes.Ldc_I4, 255), OpCodes.And, OpCodes.Conv_U1, OpCodes.Xor, OpCodes.Conv_U1, OpCodes.Stloc_1);
Emit(OpCodes.Ldloc_1, OpCodes.Ldloc_0, (OpCodes.Ldc_I4, 24), OpCodes.Shr, (OpCodes.Ldc_I4, 255), OpCodes.And, OpCodes.Conv_U1, OpCodes.Xor, OpCodes.Conv_U1, OpCodes.Stloc_1);
var loopBodyLabel = il.DefineLabel();
var conditionLabel = il.DefineLabel();
Emit(OpCodes.Ldc_I4_0, OpCodes.Stloc_2);
il.Emit(OpCodes.Br_S, conditionLabel);
il.MarkLabel(loopBodyLabel);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldloc_2);
il.Emit(OpCodes.Callvirt, typeof(string).GetProperty("Chars").GetMethod);
il.Emit(OpCodes.Stloc_3);
Emit(OpCodes.Ldloc_1, OpCodes.Ldloc_3, (OpCodes.Ldc_I4, 255), OpCodes.And, OpCodes.Conv_U1, OpCodes.Xor, OpCodes.Conv_U1, OpCodes.Stloc_1);
Emit(OpCodes.Ldloc_1, OpCodes.Ldloc_3, OpCodes.Ldc_I4_8, OpCodes.Shr, (OpCodes.Ldc_I4, 255), OpCodes.And, OpCodes.Conv_U1, OpCodes.Xor, OpCodes.Conv_U1, OpCodes.Stloc_1);
Emit(OpCodes.Ldloc_2, OpCodes.Ldc_I4_1, OpCodes.Add, OpCodes.Stloc_2);
il.MarkLabel(conditionLabel);
il.Emit(OpCodes.Ldloc_2);
il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Blt_S, loopBodyLabel);
il.Emit(OpCodes.Ldloc_1);
il.Emit(OpCodes.Ret);
}
var type = typeBuilder.CreateType();
if (access == AssemblyBuilderAccess.RunAndSave)
assembly.Save("Benchmark.Dynamic.Native.dll");
var method = type.GetMethod("Test", BindingFlags.Static | BindingFlags.Public);
return (Func<string, byte>)method.CreateDelegate(typeof(Func<string, byte>));
}
private static Expression<Func<string, byte>> CreateExpression()
{
var str = Expression.Parameter(typeof(string), "str");
var len = Expression.Variable(typeof(int), "len");
var ret = Expression.Variable(typeof(byte), "ret");
var i = Expression.Variable(typeof(int), "i");
var ch = Expression.Variable(typeof(int), "ch");
Expression.Assign(i, Expression.Constant(0));
var forBreak = Expression.Label();
var forBody = Expression.Block(
Expression.Assign(ch, Expression.Convert(Expression.Property(str, "Chars", i), typeof(int))),
Expression.ExclusiveOrAssign(ret, Expression.Convert(Expression.And(ch, Expression.Constant(0xFF)), typeof(byte))),
Expression.ExclusiveOrAssign(ret, Expression.Convert(Expression.And(Expression.RightShift(ch, Expression.Constant(8)), Expression.Constant(0xFF)), typeof(byte))),
Expression.AddAssign(i, Expression.Constant(1)));
var body = Expression.Block(
new [] { len, ret, i, ch },
Expression.Assign(len, Expression.Property(str, nameof(string.Length))),
Expression.Assign(ret, Expression.Convert(Expression.And(len, Expression.Constant(0xFF)), typeof(byte))),
Expression.ExclusiveOrAssign(ret, Expression.Convert(Expression.And(Expression.RightShift(len, Expression.Constant(8)), Expression.Constant(0xFF)), typeof(byte))),
Expression.ExclusiveOrAssign(ret, Expression.Convert(Expression.And(Expression.RightShift(len, Expression.Constant(16)), Expression.Constant(0xFF)), typeof(byte))),
Expression.ExclusiveOrAssign(ret, Expression.Convert(Expression.And(Expression.RightShift(len, Expression.Constant(24)), Expression.Constant(0xFF)), typeof(byte))),
Expression.Loop(Expression.IfThenElse(Expression.LessThan(i, len), forBody, Expression.Break(forBreak)), forBreak),
ret);
return Expression.Lambda<Func<string, byte>>(body, str);
}
private static byte NativeStatic(string str)
{
int len = str.Length;
byte ret = (byte)(len & 0xFF);
ret ^= (byte)((len >> 8) & 0xFF);
ret ^= (byte)((len >> 16) & 0xFF);
ret ^= (byte)((len >> 24) & 0xFF);
for (int i = 0; i < len; i++)
{
char ch = str[i];
ret ^= (byte)(ch & 0xFF);
ret ^= (byte)((ch >> 8) & 0xFF);
}
return ret;
}
private static unsafe byte NativeUnsafeStatic(string str)
{
int len = str.Length;
byte* bytes = (byte*)&len;
byte ret = bytes[3];
ret ^= bytes[2];
ret ^= bytes[1];
ret ^= bytes[0];
fixed (char* chars = str)
{
for (int i = 0; i < len; i++)
{
byte* ch = (byte*)&chars[i];
ret ^= ch[1];
ret ^= ch[0];
}
}
return ret;
}
private readonly string _data = string.Join(":", Enumerable.Repeat("Benchmark.CodeGeneration", 10000));
[Benchmark(Baseline = true)]
public byte Native() => NativeStatic(_data);
[Benchmark]
public byte NativeUnsafe() => NativeUnsafeStatic(_data);
[Benchmark]
public byte NativeDelegate() => _nativeDelegate(_data);
[Benchmark]
public byte LinqExpressions() => _expression(_data);
[Benchmark]
public byte ReflectionEmitExpressionRunAndSave() => _emitExpressionRunAndSave(_data);
[Benchmark]
public byte ReflectionEmitExpressionRunAndCollect() => _emitExpressionRunAndCollect(_data);
[Benchmark]
public byte ReflectionEmitExpressionRun() => _emitExpressionRun(_data);
[Benchmark]
public byte ReflectionEmitNativeRunAndSave() => _emitNativeRunAndSave(_data);
[Benchmark]
public byte ReflectionEmitNativeRunAndCollect() => _emitNativeRunAndCollect(_data);
[Benchmark]
public byte ReflectionEmitNativeRun() => _emitNativeRun(_data);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment