Created
June 8, 2018 10:25
-
-
Save reejk/0a69fa4c40a2d04a216f65e320c0a8bc to your computer and use it in GitHub Desktop.
CodeGeneration Benchmark
This file contains 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 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