注意:我的初始代码中有一个bug,用于基于实例的代码生成。请重新检查下面的代码。更改的部分是将值加载到堆栈上的顺序(即.Emit行)。答案和存储库中的代码都已修复。
它在一个int数组和一个boolean数组上执行ConsumeValue 1000万次(在我的示例中它什么也不做),对执行进行计时(它将所有代码运行一次,以消除JIT开销,从而使计时发生偏差)
F1 ints = 445ms <-- uses Convert.ToDouble
F1 bools = 351ms
F2 ints = 159ms <-- generates code on each call
F2 bools = 167ms
F3 ints = 158ms <-- caches generated code between calls
F3 bools = 163ms
代码生成的开销大约减少65%。
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
namespace ConsoleApplication15
{
class Program
{
public static void F1<T>(IList<T> values) where T : struct
{
foreach (T value in values)
ConsumeValue(Convert.ToDouble(value));
}
public static Action<T> GenerateAction<T>()
{
DynamicMethod method = new DynamicMethod(
"action", MethodAttributes.Public | MethodAttributes.Static,
CallingConventions.Standard,
typeof(void), new Type[] { typeof(T) }, typeof(Program).Module,
false);
ILGenerator il = method.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Conv_R8);
il.Emit(OpCodes.Call, typeof(Program).GetMethod("ConsumeValue"));
il.Emit(OpCodes.Ret);
return (Action<T>)method.CreateDelegate(typeof(Action<T>));
}
public static void F2<T>(IList<T> values) where T : struct
{
Action<T> action = GenerateAction<T>();
foreach (T value in values)
action(value);
}
private static Dictionary<Type, object> _Actions =
new Dictionary<Type, object>();
public static void F3<T>(IList<T> values) where T : struct
{
Object actionObject;
if (!_Actions.TryGetValue(typeof(T), out actionObject))
{
actionObject = GenerateAction<T>();
_Actions[typeof (T)] = actionObject;
}
Action<T> action = (Action<T>)actionObject;
foreach (T value in values)
action(value);
}
public static void ConsumeValue(double value)
{
}
static void Main(string[] args)
{
Stopwatch sw = new Stopwatch();
int[] ints = Enumerable.Range(1, 10000000).ToArray();
bool[] bools = ints.Select(i => i % 2 == 0).ToArray();
for (int pass = 1; pass <= 2; pass++)
{
sw.Reset();
sw.Start();
F1(ints);
sw.Stop();
if (pass == 2)
Console.Out.WriteLine("F1 ints = "
+ sw.ElapsedMilliseconds + "ms");
sw.Reset();
sw.Start();
F1(bools);
sw.Stop();
if (pass == 2)
Console.Out.WriteLine("F1 bools = "
+ sw.ElapsedMilliseconds + "ms");
sw.Reset();
sw.Start();
F2(ints);
sw.Stop();
if (pass == 2)
Console.Out.WriteLine("F2 ints = "
+ sw.ElapsedMilliseconds + "ms");
sw.Reset();
sw.Start();
F2(bools);
sw.Stop();
if (pass == 2)
Console.Out.WriteLine("F2 bools = "
+ sw.ElapsedMilliseconds + "ms");
sw.Reset();
sw.Start();
F3(ints);
sw.Stop();
if (pass == 2)
Console.Out.WriteLine("F3 ints = "
+ sw.ElapsedMilliseconds + "ms");
sw.Reset();
sw.Start();
F3(bools);
sw.Stop();
if (pass == 2)
Console.Out.WriteLine("F3 bools = "
+ sw.ElapsedMilliseconds + "ms");
}
}
}
}
请注意,如果使GenerationAction、F2/3和ConsumeValue非静态,则必须稍微更改代码:
-
Action<T>
声明变成
Action<Program, T>
-
更改DynamicMethod的创建以包含“this”参数:
DynamicMethod method = new DynamicMethod(
"action", MethodAttributes.Public | MethodAttributes.Static,
CallingConventions.Standard,
typeof(void), new Type[] { typeof(Program), typeof(T) },
typeof(Program).Module,
false);
-
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Conv_R8);
il.Emit(OpCodes.Call, typeof(Program).GetMethod("ConsumeValue"));
il.Emit(OpCodes.Ret);
-
action(this, value);
以下是非静态方法的完整更改程序:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
namespace ConsoleApplication15
{
class Program
{
public void F1<T>(IList<T> values) where T : struct
{
foreach (T value in values)
ConsumeValue(Convert.ToDouble(value));
}
public Action<Program, T> GenerateAction<T>()
{
DynamicMethod method = new DynamicMethod(
"action", MethodAttributes.Public | MethodAttributes.Static,
CallingConventions.Standard,
typeof(void), new Type[] { typeof(Program), typeof(T) },
typeof(Program).Module,
false);
ILGenerator il = method.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Conv_R8);
il.Emit(OpCodes.Call, typeof(Program).GetMethod("ConsumeValue"));
il.Emit(OpCodes.Ret);
return (Action<Program, T>)method.CreateDelegate(
typeof(Action<Program, T>));
}
public void F2<T>(IList<T> values) where T : struct
{
Action<Program, T> action = GenerateAction<T>();
foreach (T value in values)
action(this, value);
}
private static Dictionary<Type, object> _Actions =
new Dictionary<Type, object>();
public void F3<T>(IList<T> values) where T : struct
{
Object actionObject;
if (!_Actions.TryGetValue(typeof(T), out actionObject))
{
actionObject = GenerateAction<T>();
_Actions[typeof (T)] = actionObject;
}
Action<Program, T> action = (Action<Program, T>)actionObject;
foreach (T value in values)
action(this, value);
}
public void ConsumeValue(double value)
{
}
static void Main(string[] args)
{
Stopwatch sw = new Stopwatch();
Program p = new Program();
int[] ints = Enumerable.Range(1, 10000000).ToArray();
bool[] bools = ints.Select(i => i % 2 == 0).ToArray();
for (int pass = 1; pass <= 2; pass++)
{
sw.Reset();
sw.Start();
p.F1(ints);
sw.Stop();
if (pass == 2)
Console.Out.WriteLine("F1 ints = "
+ sw.ElapsedMilliseconds + "ms");
sw.Reset();
sw.Start();
p.F1(bools);
sw.Stop();
if (pass == 2)
Console.Out.WriteLine("F1 bools = "
+ sw.ElapsedMilliseconds + "ms");
sw.Reset();
sw.Start();
p.F2(ints);
sw.Stop();
if (pass == 2)
Console.Out.WriteLine("F2 ints = "
+ sw.ElapsedMilliseconds + "ms");
sw.Reset();
sw.Start();
p.F2(bools);
sw.Stop();
if (pass == 2)
Console.Out.WriteLine("F2 bools = "
+ sw.ElapsedMilliseconds + "ms");
sw.Reset();
sw.Start();
p.F3(ints);
sw.Stop();
if (pass == 2)
Console.Out.WriteLine("F3 ints = "
+ sw.ElapsedMilliseconds + "ms");
sw.Reset();
sw.Start();
p.F3(bools);
sw.Stop();
if (pass == 2)
Console.Out.WriteLine("F3 bools = "
+ sw.ElapsedMilliseconds + "ms");
}
}
}
}