Ответ 1
ПРИМЕЧАНИЕ. В исходном коде была ошибка при генерации кода на основе экземпляра. Пожалуйста, проверьте код ниже. Измененная часть - это порядок загрузки значений в стек (т.е. строки .Emit). Код в ответе и хранилище были исправлены.
Если вы хотите пойти по пути генерации кода, как вы намекнули в своем вопросе, вот пример кода:
Он выполняет ConsumeValue (который ничего не делает в моем примере) 10 миллионов раз для массива int и массива логических значений, синхронизируя выполнение (он запускает весь код один раз, чтобы удалить издержки 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% меньше.
Код доступен в моем репозитории Mercurial здесь: http://hg.vkarlsen.no/hgweb.cgi/Qaru, просмотрите его, найдя номер своего SO вопроса. К сожалению, этот репозиторий больше не доступен, и у меня больше нет копии кода из-за неудачного резервного копирования
Код:
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); // get value passed to action
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); // get "this" il.Emit(OpCodes.Ldarg_1); // get value passed to action il.Emit(OpCodes.Conv_R8); il.Emit(OpCodes.Call, typeof(Program).GetMethod("ConsumeValue")); il.Emit(OpCodes.Ret);
Передайте "this" действию, когда оно вызывается:
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); // get "this"
il.Emit(OpCodes.Ldarg_1); // get value passed to action
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");
}
}
}
}