想象一下以下简单的代码:
public void F<T>(IList<T> values) where T : struct
{
foreach (T value in values)
{
double result;
if (TryConvertToDouble((object)value, out result))
{
ConsumeValue(result);
}
}
}
public void ConsumeValue(double value)
{
}
Run Code Online (Sandbox Code Playgroud)
上面代码的问题是转换为对象,这导致在循环中装箱.
有没有办法实现相同的功能,即为ConsumeValue提供所有值而不需要在foreach循环中使用装箱?注意,F必须是通用方法.
只要在循环外执行一次,我就可以使用昂贵的准备代码.例如,如果需要发出一个奇特的动态方法,那么只做一次就可以了.
编辑
T保证是某种数字类型或bool.
动机.想象一下元数据驱动的应用程序,其中代理报告数据流,其中数据项类型是基于数据流元数据动态发出的.想象一下,还有规范化引擎,它知道根据某种算法规范化数字数据流.传入的数字数据流的类型仅在运行时是已知的,并且可以指向该数据类型的通用方法.然而,规范化器期望双倍并产生双精度.这是一个非常高级别的描述,请不要深入研究.
EDIT2
关于演员要加倍.实际上我们有一个方法,使用以下签名转换为double:
bool TryConvertToDouble(object value, out double result);
Run Code Online (Sandbox Code Playgroud)
我应该首先在示例中使用它,但我想节省空间并编写一些不起作用的东西.现在修好了.谢谢你的注意.
EDIT3
伙计们,目前的实施确实包装了价值观.即使我没有关于它的性能惩罚的探查者的判断(如果有的话),我仍然有兴趣知道是否有没有装箱的解决方案(并且没有转换为字符串).让我称之为纯粹的学术兴趣.这真的让我感兴趣,因为在C++中使用模板这样的东西是微不足道的,但是,当然,我还没有开始关于什么是更好的.NET泛型或C++模板的另一个愚蠢和毫无意义的论点.请忽略这最后一句话.
EDIT4
感谢/sf/users/18721/提供了答案.实际上,我已经使用他的代码示例编写了一个这样的简单类:
public static class Utils<T>
{
private static class ToDoubleConverterHolder
{
internal static Func<T, double> Value = EmitConverter();
private static Func<T, double> EmitConverter()
{
ThrowIfNotConvertableToDouble(typeof(T));
var method = new DynamicMethod(string.Empty, typeof(double), TypeArray<T>.Value);
var il = method.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
if (typeof(T) != typeof(double))
{
il.Emit(OpCodes.Conv_R8);
}
il.Emit(OpCodes.Ret);
return (Func<T, double>)method.CreateDelegate(typeof(Func<T, double>));
}
}
public static double ConvertToDouble(T value)
{
return ToDoubleConverterHolder.Value(value);
}
}
Run Code Online (Sandbox Code Playgroud)
哪里:
new[]{ typeof(T) }Utils.ConvertToDouble方法以最有效的方式将任何数值转换为double,由此问题的答案显示.
它就像一个魅力 - 谢谢你.
注意:我的初始代码中存在基于实例的代码生成的错误.请重新检查以下代码.更改的部分是将值加载到堆栈上的顺序(即.Emit行).答案和存储库中的代码都已修复.
如果你想要代码生成的路径,正如你在你的问题中提示的那样,这里是示例代码:
它执行ConsumeValue(在我的例子中什么都不做)1000万次,在一个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
Run Code Online (Sandbox Code Playgroud)
代码生成开销大约减少65%.
该代码可以从我的Mercurial存储库获得:http://hg.vkarlsen.no/hgweb.cgi/StackOverflow,通过查找您的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");
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,如果您使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);
Run Code Online (Sandbox Code Playgroud)更改说明以在正确的时间加载正确的值:
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);
Run Code Online (Sandbox Code Playgroud)无论何时调用,都将"this"传递给动作:
action(this, value);
Run Code Online (Sandbox Code Playgroud)这是非静态方法的完整更改程序:
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");
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
这是一个很好的问题,我也有这个任务,我想出了使用编译的Linq Expressions进行值类型与通用类型参数之间的任意转换,从而避免了装箱。该解决方案非常有效且快速。它在单例中为每个值类型存储一个已编译的lambda。用法干净且可读。
这是一个简单的类,可以很好地完成工作:
public sealed class BoxingSafeConverter<TIn, TOut>
{
public static readonly BoxingSafeConverter<TIn, TOut> Instance = new BoxingSafeConverter<TIn, TOut>();
private readonly Func<TIn, TOut> convert;
public Func<TIn, TOut> Convert
{
get { return convert; }
}
private BoxingSafeConverter()
{
if (typeof (TIn) != typeof (TOut))
{
throw new InvalidOperationException("Both generic type parameters must represent the same type.");
}
var paramExpr = Expression.Parameter(typeof (TIn));
convert =
Expression.Lambda<Func<TIn, TOut>>(paramExpr, // this conversion is legal as typeof(TIn) = typeof(TOut)
paramExpr)
.Compile();
}
}
Run Code Online (Sandbox Code Playgroud)
现在想像一下,您想存储一些对象和双打,而又不想将双打装箱。您可以通过以下方式使用通用的getter和setter编写此类:
public class MyClass
{
readonly List<double> doubles = new List<double>(); // not boxed doubles
readonly List<object> objects = new List<object>(); // all other objects
public void BoxingSafeAdd<T>(T val)
{
if (typeof (T) == typeof (double))
{
// T to double conversion
doubles.Add(BoxingSafeConverter<T, double>.Instance.Convert(val));
return;
}
objects.Add(val);
}
public T BoxingSafeGet<T>(int index)
{
if (typeof (T) == typeof (double))
{
// double to T conversion
return BoxingSafeConverter<double, T>.Instance.Convert(doubles[index]);
}
return (T) objects[index]; // boxing-unsage conversion
}
}
Run Code Online (Sandbox Code Playgroud)
以下是MyClass的一些简单性能和内存测试,这些测试表明使用取消装箱的值可以节省大量内存,减少GC压力,并且性能开销非常小:大约5-10%。
1.装箱:
const int N = 1000000;
MyClass myClass = new MyClass();
double d = 0.0;
var sw = Stopwatch.StartNew();
for (int i = 0; i < N; i++, d += 0.1)
{
myClass.BoxingSafeAdd((object)d);
}
Console.WriteLine("Time: {0} ms", sw.ElapsedMilliseconds);
Console.WriteLine("Memory: {0} MB.", (double)GC.GetTotalMemory(false) / 1024 / 1024);
Run Code Online (Sandbox Code Playgroud)
结果:
Time: 130 ms
Memory: 19.7345771789551 MB
Run Code Online (Sandbox Code Playgroud)
2.无拳击
const int N = 1000000;
MyClass myClass = new MyClass();
double d = 0.0;
var sw = Stopwatch.StartNew();
for (int i = 0; i < N; i++, d += 0.1)
{
myClass.BoxingSafeAdd(d);
}
Console.WriteLine("Time: {0} ms", sw.ElapsedMilliseconds);
Console.WriteLine("Memory: {0} MB", (double)GC.GetTotalMemory(false) / 1024 / 1024);
Run Code Online (Sandbox Code Playgroud)
结果:
Time: 144 ms
Memory: 12.4955024719238 MB
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
7987 次 |
| 最近记录: |