原文链接:http://www.cnblogs.com/fish-li/archive/2013/02/18/2916253.html
优化反射性能的总结(上)
阅读目录
开始
用Emit方法优化反射
Delegate.CreateDelegate也能创建委托
用Delegate.CreateDelegate优化反射
完整的属性优化方案
委托方案的后续问题
缓存的线程并发问题
小结
招聘信息
反射是一种很重要的技术,然而它与直接调用相比性能要慢很多,因此如何优化反射性能也就成为一个不得不面对的问题。 目前最常见的优化反射性能的方法就是采用委托:用委托的方式调用需要反射调用的方法(或者属性、字段)。
那么如何得到委托呢? 目前最常见也就是二种方法:Emit, ExpressionTree 。其中ExpressionTree可认为是Emit方法的简化版本, 所以Emit是最根本的方法,它采用在运行时动态构造一段IL代码来包装需要反射调用的代码, 这段动态生成的代码满足某个委托的签名,因此最后可以采用委托的方式代替反射调用。
回到顶部
用Emit方法优化反射
如果我们需要设计自己的数据访问层,那么就需要动态创建所有的数据实体对象,尤其是还要为每个数据实体对象的属性赋值, 这里就要涉及用反射的方法对属性执行写操作,为了优化这种反射场景的性能,我们可以用下面的方法来实现:
public delegate void SetValueDelegate(object target, object arg);
public static class DynamicMethodFactory
{
public static SetValueDelegate CreatePropertySetter(PropertyInfo property)
{
if( property == null )
throw new ArgumentNullException("property");
if( !property.CanWrite )
return null;
MethodInfo setMethod = property.GetSetMethod(true);
DynamicMethod dm = new DynamicMethod("PropertySetter", null,
new Type[] { typeof(object), typeof(object) },
property.DeclaringType, true);
ILGenerator il = dm.GetILGenerator();
if( !setMethod.IsStatic ) {
il.Emit(OpCodes.Ldarg_0);
}
il.Emit(OpCodes.Ldarg_1);
EmitCastToReference(il, property.PropertyType);
if( !setMethod.IsStatic && !property.DeclaringType.IsValueType ) {
il.EmitCall(OpCodes.Callvirt, setMethod, null);
}
else
il.EmitCall(OpCodes.Call, setMethod, null);
il.Emit(OpCodes.Ret);
return (SetValueDelegate)dm.CreateDelegate(typeof(SetValueDelegate));
}
private static void EmitCastToReference(ILGenerator il, Type type)
{
if( type.IsValueType )
il.Emit(OpCodes.Unbox_Any, type);
else
il.Emit(OpCodes.Castclass, type);
}
}
现在可以用下面的测试代码检验委托调用带来的性能改进:
Console.WriteLine(System.Runtime.InteropServices.RuntimeEnvironment.GetSystemVersion());
int count = 1000000;
OrderInfo testObj = new OrderInfo();
PropertyInfo propInfo = typeof(OrderInfo).GetProperty("OrderID");
Console.Write("直接访问花费时间: ");
Stopwatch watch1 = Stopwatch.StartNew();
for( int i = 0; i < count; i++ )
testObj.OrderID = 123;
watch1.Stop();
Console.WriteLine(watch1.Elapsed.ToString());
SetValueDelegate setter2 = DynamicMethodFactory.CreatePropertySetter(propInfo);
Console.Write("EmitSet花费时间: ");
Stopwatch watch2 = Stopwatch.StartNew();
for( int i = 0; i < count; i++ )
setter2(testObj, 123);
watch2.Stop();
Console.WriteLine(watch2.Elapsed.ToString());
Console.Write("纯反射花费时间: ");
Stopwatch watch3 = Stopwatch.StartNew();
for( int i = 0; i < count; i++ )
propInfo.SetValue(testObj, 123, null);
watch3.Stop();
Console.WriteLine(watch3.Elapsed.ToString());
Console.WriteLine("-------------------");
Console.WriteLine("{0} / {1} = {2}",
watch3.Elapsed.ToString(),
watch1.Elapsed.ToString(),
watch3.Elapsed.TotalMilliseconds / watch1.Elapsed.TotalMilliseconds);
Console.WriteLine("{0} / {1} = {2}",
watch3.Elapsed.ToString(),
watch2.Elapsed.ToString(),
watch3.Elapsed.TotalMilliseconds / watch2.Elapsed.TotalMilliseconds);
Console.WriteLine("{0} / {1} = {2}",
watch2.Elapsed.ToString(),
watch1.Elapsed.ToString(),
watch2.Elapsed.TotalMilliseconds / watch1.Elapsed.TotalMilliseconds);
我用VS2008 (.net 3.5 , CLR 2.0) 测试可以得到以下结果:
从结果可以看出:
1. 反射调用所花时间是直接调用的2629倍,
2. 反射调用所花时间是Emit生成的Set委托代码的82倍,
3. 运行Emit生成的Set委托代码所花时间是直接调用的31倍。
虽然Emit比直接调用还有30倍的差距,但还是比反射调用快80倍左右。
有意思的是,同样的代码,如果用VS2012 ( .net 4.5 , CLR 4.0) 测试可以得到以下结果:
感谢zhangweiwen 在博客中展示了CRL 4.0对反射的性能改进, 在他的博客中还提供了一种采用表达式树的优化版本,以及包含一个泛型的强类型的版本。
回到顶部
Delegate.CreateDelegate也能创建委托
如果我们观察CreatePropertySetter的实现代码,发现这个方法的本质就是创建一个委托:
public static SetValueDelegate CreatePropertySetter(PropertyInfo property)
{
// ..... 省略前面已贴过的代码
return (SetValueDelegate)dm.CreateDelegate(typeof(SetValueDelegate));
}
看到这里,让我想起Delegate.CreateDelegate方法也能创建一个委托,例如:
OrderInfo testObj = new OrderInfo();
PropertyInfo propInfo = typeof(OrderInfo).GetProperty("OrderID");
Action setter = (Action)Delegate.CreateDelegate(
typeof(Action), null, propInfo.GetSetMethod());
setter(testObj, 123);
显然,这是一种很直观的方法,可以得到一个强类型的委托。
然而,这种方法仅限有一种适用场景:明确知道要访问某个类型的某个属性或者方法,因为我们要提供类型参数。 例如:我要写个关键字过滤的HttpMoudle,它需要修改HttpRequest.Form对象的IsReadOnly属性,由于IsReadOnly在NameObjectCollectionBase类型中已申明为protected访问级别, 所以我只能反射操作它了,而且还需要很频繁的设置它。
在绝大部分反射场景中,例如数据访问层中从DataReader或者DataRow加载数据实体, 我们不可能事先知道要加载哪些类型,更不可能知道要加载哪些数据成员,因此就不可能给泛型委托的类型参数赋值, 这个方法看起来也就行不通了。
如果您不信的话,可以看下面修改后的代码:
OrderInfo testObj = new OrderInfo();
PropertyInfo propInfo = typeof(OrderInfo).GetProperty("OrderID");
//Action setter = (Action)Delegate.CreateDelegate(
// typeof(Action), null, propInfo.GetSetMethod());
Action