好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

优化反射性能的总结(上)

优化反射性能的总结(上)

优化反射性能的总结(上)

优化反射性能的总结(上)

阅读目录

开始 用Emit方法优化反射 Delegate.CreateDelegate也能创建委托 用Delegate.CreateDelegate优化反射 完整的属性优化方案 委托方案的后续问题 缓存的线程并发问题 小结 招聘信息

反射是一种很重要的技术,然而它与直接调用相比性能要慢很多,因此如何优化反射性能也就成为一个不得不面对的问题。 目前最常见的优化反射性能的方法就是采用委托:用委托的方式调用需要反射调用的方法(或者属性、字段)。

那么如何得到委托呢? 目前最常见也就是二种方法:Emit, ExpressionTree 。其中ExpressionTree可认为是Emit方法的简化版本, 所以Emit是最根本的方法,它采用在运行时动态构造一段IL代码来包装需要反射调用的代码, 这段动态生成的代码满足某个委托的签名,因此最后可以采用委托的方式代替反射调用。

回到顶部

用Emit方法优化反射

如果我们需要设计自己的数据访问层,那么就需要动态创建所有的数据实体对象,尤其是还要为每个数据实体对象的属性赋值, 这里就要涉及用反射的方法对属性执行写操作,为了优化这种反射场景的性能,我们可以用下面的方法来实现: 

现在可以用下面的测试代码检验委托调用带来的性能改进: 

我用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  <  OrderInfo ,  int  >  setter  =  ( Action  <  OrderInfo ,  int  > ) Delegate  . CreateDelegate(
     typeof ( Action  <  OrderInfo ,  int  > ),  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<OrderInfo, int> setter = (Action<OrderInfo, int>)Delegate.CreateDelegate(
//    typeof(Action<OrderInfo, int>), null, propInfo.GetSetMethod());

  Action  <  object ,  object  >  setter  =  ( Action  <  object ,  object  > ) Delegate  . CreateDelegate(
     typeof ( Action  <  object ,  object  > ),  null , propInfo . GetSetMethod());

setter(testObj,  123 );

 Console  . WriteLine(testObj . OrderID);

虽然能通过编译,但会在运行时报错:

在很多时候,我们只能在运行时得到以Type对象表示的类型,接受object类型才是通用的解决方案。 然而,前面的代码证明了我们不能简单将委托类型从Action<OrderInfo, int>修改为Action<object, object> 。

真的没有办法了吗?

虽然Emit已是很成熟的优化方案,可我还是希望试试 Delegate.CreateDelegate !

回到顶部

用Delegate.CreateDelegate优化反射

当我们用Delegate.CreateDelegate从一个MethodInfo对象创建委托时, 委托的签名必须和MethodInfo表示的方法签名相匹配(有可能不一致), 所以这种方法得到的委托注定是一种强类型的委托。 现在的问题是:我们在运行时构造与指定MethodInfo匹配的委托, 如何将Type对象转换成泛型委托的类型参数?

为了解决这个问题,我采用了泛型类来解决泛型委托的类型参数问题:

 public class   SetterWrapper  < TTarget, TValue > 
 {
     private   Action  < TTarget, TValue >  _setter;

     public  SetterWrapper( PropertyInfo  propertyInfo)
    {
         if ( propertyInfo  ==   null  )
             throw new   ArgumentNullException ( "propertyInfo" );

         if ( propertyInfo . CanWrite  ==   false  )
             throw new   NotSupportedException ( "属性不支持写操作。" );

         MethodInfo  m  =  propertyInfo . GetSetMethod( true );
        _setter  =  ( Action  < TTarget, TValue > ) Delegate  . CreateDelegate( typeof ( Action  < TTarget, TValue > ),  null , m);
    }
    
     public void  SetValue(TTarget target, TValue val)
    {
        _setter(target, val);
    }

我用泛型类把Delegate.CreateDelegate的问题解决了,但是如何创建这个类型的实例呢?
可以用Type.MakeGenericType()方法来解决:

 public static   object  CreatePropertySetterWrapper( PropertyInfo  propertyInfo)
{
     if ( propertyInfo  ==   null  )
         throw new   ArgumentNullException ( "propertyInfo" );
     if ( propertyInfo . CanWrite  ==   false  )
         throw new   NotSupportedException ( "属性不支持写操作。" );

     MethodInfo  mi  =  propertyInfo . GetSetMethod( true );

     if ( mi . GetParameters() . Length  >   1  )
         throw new   NotSupportedException ( "不支持构造索引器属性的委托。" );

     Type  instanceType  =   typeof ( SetterWrapper  < , > ) . MakeGenericType(propertyInfo . DeclaringType, propertyInfo . PropertyType);
     return   Activator  . CreateInstance(instanceType, propertyInfo);
}

现在问题并没有结束,我又如何调用那些泛型类型实例的委托呢?
这里还有另一个问题要解决:调用方法需要支持object类型(满足通用性)。
我想到了定义一个接口来解决:

 public interface   ISetValue
 {
     void  Set( object  target,  object  val);
}

然后让SetterWrapper实现ISetValue接口:

 public class   SetterWrapper  < TTarget, TValue >  :  ISetValue
 {
     // ..... 省略前面已贴过的代码

      void   ISetValue  . Set( object  target,  object  val)
    {
        _setter((TTarget)target, (TValue)val);
    }
}

还有前面的CreatePropertySetterWrapper方法也需要再次调整返回值类型:

 public static   ISetValue  CreatePropertySetterWrapper( PropertyInfo  propertyInfo)
{
     // ..... 省略前面已贴过的代码
      return  ( ISetValue ) Activator  . CreateInstance(instanceType, propertyInfo);
}

考虑到有些特定场景下需要用反射的方式重复操作某一个属性,使用强类型的方法可以避免拆箱装箱,
所以我保留了前面的SetValue方法,让它提供更好的性能,满足一些特定场景的需要。
因此,现在的SetterWrapper类型有二种使用方法,可以提供二种性能不同的实现方法。

现在可以增加二段测试代码来测试它的性能了:

 Console  . Write( "泛型委托花费时间:       " );
 SetterWrapper  <  OrderInfo ,  int  >  setter3  =   new   SetterWrapper  <  OrderInfo ,  int  > (propInfo);
 Stopwatch  watch4  =   Stopwatch  . StartNew();

 for (  int  i  =   0 ; i  <  count; i ++  )
    setter3 . SetValue(testObj,  123 );

watch4 . Stop();
 Console  . WriteLine(watch4 . Elapsed . ToString());


 Console  . Write( "通用接口花费时间:       " );
 ISetValue  setter4  =   GetterSetterFactory  . CreatePropertySetterWrapper(propInfo);
 Stopwatch  watch5  =   Stopwatch  . StartNew();

 for (  int  i  =   0 ; i  <  count; i ++  )
    setter4 . Set(testObj,  123 );

watch5 . Stop();
 Console  . WriteLine(watch5 . Elapsed . ToString());

测试结果如下:

测试结果表明:强类型的泛型委托的速度比Emit生成的Set委托要快,但是基于通用接口的方法调用由于多了一层包装就比Emit方案要略慢一点。

回到顶部

完整的属性优化方案

前面介绍了为属性赋值这类反射案例的优化方案,那么怎么优化读取属性的反射操作呢?

其实思路差不多:
1. 在泛型类中调用Delegate.CreateDelegate,得到一个Func<TTarget, TValue>,
2. 定义一个IGetValue接口,提供一个方法: object Get(object target);
3. 让泛型类实现IGetValue接口
4. 提供一个工厂方法实例化泛型类的实例。
相关代码如下:

 public interface   IGetValue
 {
     object  Get( object  target);
}

 public static class   GetterSetterFactory
 {
     public static   IGetValue  CreatePropertyGetterWrapper( PropertyInfo  propertyInfo)
    {
         if ( propertyInfo  ==   null  )
             throw new   ArgumentNullException ( "propertyInfo" );
         if ( propertyInfo . CanRead  ==   false  )
             throw new   InvalidOperationException ( "属性不支持读操作。" );

         MethodInfo  mi  =  propertyInfo . GetGetMethod( true );

         if ( mi . GetParameters() . Length  >   0  )
             throw new   NotSupportedException ( "不支持构造索引器属性的委托。" );
        
         Type  instanceType  =   typeof ( GetterWrapper  < , > ) . MakeGenericType(propertyInfo . DeclaringType, propertyInfo . PropertyType);
         return  ( IGetValue ) Activator  . CreateInstance(instanceType, propertyInfo);
    }
}

 public class   GetterWrapper  < TTarget, TValue >  :  IGetValue 
{
     private   Func  < TTarget, TValue >  _getter;

     public  GetterWrapper( PropertyInfo  propertyInfo)
    {
         if ( propertyInfo  ==   null  )
             throw new   ArgumentNullException ( "propertyInfo" );

         if ( propertyInfo . CanRead  ==   false  )
             throw new   InvalidOperationException ( "属性不支持读操作。" );

         MethodInfo  m  =  propertyInfo . GetGetMethod( true );
        _getter  =  ( Func  < TTarget, TValue > ) Delegate  . CreateDelegate( typeof ( Func  < TTarget, TValue > ),  null , m);
    }
    
     public  TValue GetValue(TTarget target)
    {
         return  _getter(target);
    }
     object   IGetValue  . Get( object  target)
    {
         return  _getter((TTarget)target);
    }
}

前面的代码优化了实例属性的反射读写性能问题,但是还有极少数时候我们还需要处理静态属性,那么我们还需要再定义二个泛型类来解决:

 public class   StaticGetterWrapper  < TValue >  :  IGetValue 
{
     private   Func  < TValue >  _getter;

     // ............
 }

 public class   StaticSetterWrapper  < TValue >  :  ISetValue 
{
     private   Action  < TValue >  _setter;

     // ............
 }

前面看到的工厂方法也要调整,完整代码如下:

 public static   ISetValue  CreatePropertySetterWrapper( PropertyInfo  propertyInfo)
{
     if ( propertyInfo  ==   null  )
         throw new   ArgumentNullException ( "propertyInfo" );
     if ( propertyInfo . CanWrite  ==   false  )
         throw new   NotSupportedException ( "属性不支持写操作。" );

     MethodInfo  mi  =  propertyInfo . GetSetMethod( true );

     if ( mi . GetParameters() . Length  >   1  )
         throw new   NotSupportedException ( "不支持构造索引器属性的委托。" );

     if ( mi . IsStatic ) {
         Type  instanceType  =   typeof ( StaticSetterWrapper  <> ) . MakeGenericType(propertyInfo . PropertyType);
         return  ( ISetValue ) Activator  . CreateInstance(instanceType, propertyInfo);
    }
     else  {
         Type  instanceType  =   typeof ( SetterWrapper  < , > ) . MakeGenericType(propertyInfo . DeclaringType, propertyInfo . PropertyType);
         return  ( ISetValue ) Activator  . CreateInstance(instanceType, propertyInfo);
    }
}

回到顶部

委托方案的后续问题

前面的代码解决了属性的读写问题,然而使用它们还很不方便:每次都要创建一个ISetValue接口的实例,再调用它的方法。 其实这也是委托方案共有的问题:我们需要为每个属性的读写操作分别创建不同的委托,而且委托太零散了。

如何将属性与创建好的委托关联起来呢?(创建委托也是需要时间的)
我想所有人都会想到用字典来保存。
是的,好像也只有这一种方法了。
为了提高性能,我改进了工厂类,缓存了包含委托的实例,
为了方便使用前面的方法,我提供了一些扩展方法:

 public static class   GetterSetterFactory
 {
     private static readonly   Hashtable  s_getterDict  =   Hashtable  . Synchronized( new   Hashtable ( 10240 ));
     private static readonly   Hashtable  s_setterDict  =   Hashtable  . Synchronized( new   Hashtable ( 10240 ));

     internal static   IGetValue  GetPropertyGetterWrapper( PropertyInfo  propertyInfo)
    {
         IGetValue  property  =  ( IGetValue )s_getterDict[propertyInfo];
         if ( property  ==   null  ) {
            property  =  CreatePropertyGetterWrapper(propertyInfo);
            s_getterDict[propertyInfo]  =  property;
        }
         return  property;
    }

     internal static   ISetValue  GetPropertySetterWrapper( PropertyInfo  propertyInfo)
    {
         ISetValue  property  =  ( ISetValue )s_setterDict[propertyInfo];
         if ( property  ==   null  ) {
            property  =  CreatePropertySetterWrapper(propertyInfo);
            s_setterDict[propertyInfo]  =  property;
        }
         return  property;
    }
}

 public static class   PropertyExtensions
 {
     public static object  FastGetValue( this   PropertyInfo  propertyInfo,  object  obj)
    {
         if ( propertyInfo  ==   null  )
             throw new   ArgumentNullException ( "propertyInfo" );

         return   GetterSetterFactory  . GetPropertyGetterWrapper(propertyInfo) . Get(obj);
    }

     public static void  FastSetValue( this   PropertyInfo  propertyInfo,  object  obj,  object  value)
    {
         if ( propertyInfo  ==   null  )
             throw new   ArgumentNullException ( "propertyInfo" );

         GetterSetterFactory  . GetPropertySetterWrapper(propertyInfo) . Set(obj, value);
    }
}

说明:我在缓存的设计上并没有使用泛型Dictionary,而是使用了Hashtable。
我承认在简单的单线程测试中,Dictionary要略快于Hashtable 。

再来测试一下FastSetValue的性能吧,毕竟大多数时候我会使用这个扩展方法。
我又在测试代码中增加了一段:

propInfo . FastSetValue(testObj,  123 );
 Console  . Write( "FastSet花费时间:       " );
 Stopwatch  watch6  =   Stopwatch  . StartNew();

 for (  int  i  =   0 ; i  <  count; i ++  )
    propInfo . FastSetValue(testObj,  123 );

watch6 . Stop();
 Console  . WriteLine(watch6 . Elapsed . ToString());

测试结果如下:

测试结果表明:虽然通用接口ISetValue将反射性能优化了37倍,但是最终的FastSetValue将这个数字减少到还不到7倍(在CLR4中还不到5倍)。

看到这个结果您是否也比较郁闷:优化了几十倍的结果,最后却丢了大头,只得到一个零头!

中间那30倍的时间是哪里消耗了?
1.  Hashtable的查找时间。
2. 代码的执行路径变长了。

代码的执行路径变长了,我想所有人应该都能接受:为了简化调用并配合缓存一起工作,代码的执行路径确实变长了。

Hashtable的查找时间应该很快吧? 您是不是也这样想呢? 
为了看看Hashtable的查找时间,我又加了一点测试代码:

 Hashtable  table  =   new   Hashtable ();
table[propInfo]  =   new object ();
 Console  . Write( "Hashtable花费时间:      " );
 Stopwatch  watch7  =   Stopwatch  . StartNew();

 for (  int  i  =   0 ; i  <  count; i ++  ) {
     object  val  =  table[propInfo];
}
watch7 . Stop();
 Console  . WriteLine(watch7 . Elapsed . ToString());

现在运行测试代码的结果如下:

确实,大部分时间消耗在Hashtable的查找上!

回到顶部

缓存的线程并发问题

集合不仅仅只有查找开销, 在多线程环境中,我们还要考虑并发性。

看到许多人做性能测试时,总是喜欢写个控制台程序,然后再来个for循环,执行多少万次!
我认为  这样的结果只能反映代码在单线程环境下的性能 ,在多线程下,结果可能会有较大的差别, 当然了,多线程测试的确很复杂,也很难得到准确的数字。  但是我们的设计不能不考虑多线程下的并发问题。

虽然我也在单线程环境下测试过Dictionary<TKey, TValue>的性能,的确要比Hashtable略好点。
但是MSDN上对Dictionary的线程安全的描述是这样的:

此类型的公共静态(在 Visual Basic 中为 Shared)成员是线程安全的。但不能保证任何实例成员是线程安全的。 

只要不修改该集合,Dictionary<(Of <(TKey, TValue>)>) 就可以同时支持多个阅读器。即便如此,从头到尾对一个集合进行枚举本质上并不是一个线程安全的过程。当出现枚举与写访问互相争用这种极少发生的情况时,必须在整个枚举过程中锁定集合。若要允许多个线程访问集合以进行读写操作,则必须实现自己的同步。

而MSDN对Hashtable的线程安全的描述却是:

Hashtable 是线程安全的,可由多个读取器线程和一个写入线程使用。多线程使用时,如果只有一个线程执行写入(更新)操作,则它是线程安全的,从而允许进行无锁定的读取(若编写器序列化为 Hashtable)。若要支持多个编写器,如果没有任何线程在读取 Hashtable 对象,则对 Hashtable 的所有操作都必须通过 Synchronized 方法返回的包装完成。 

从头到尾对一个集合进行枚举本质上并不是一个线程安全的过程。即使一个集合已进行同步,其他线程仍可以修改该集合,这将导致枚举数引发异常。若要在枚举过程中保证线程安全,可以在整个枚举过程中锁定集合,或者捕捉由于其他线程进行的更改而引发的异常。

显然,二个集合都不能完全支持多线程的并发读写。
虽然Hashtable提供同步包装的线程安全版本,但是内部还是在使用锁来保证同步的!
没办法,在多线程环境中,任何复杂数据结构都有线程安全问题。

如何保证集合在并发操作中数据的同步呢?
是lock还是ReaderWriterLock?
显然前者的实现较为简单,所以它成了绝大多数人的首选。
在.net4中,ConcurrentDictionary是另一个新的首选方法。

由于Dictionary只支持并发的读操作,所以只要涉及到写操作,它就不安全了,
因此最安全地做法也只好在  读和写  操作上都加lock,否则就不安全了。

而Hashtable则不同,它的内部数据结构支持一个线程写入的同时允许多个线程并发读取,所以只要在写操作上加lock就可以实现线程同步, Hashtable的线程安全版本也就是这样实现的。 这也是我选择Hashtable的原因。

回到顶部

小结

在这篇博客中,我演示了二种不同的反射优化方法:
1. 基于Emit的动态生成符合委托签名的IL代码。
2. 使用Delegate.CreateDelegate直接创建委托。

这是二种截然不同的思路:
1. Emit方法,需要先定义一个委托签名,然后生成符合委托签名的IL代码。
2. CreateDelegate可以直接生成委托,但需要借用泛型类解决委托的类型参数问题,最后为了能通用,需要以接口方式调用强类型委托。

虽然我们可以使用任何一种方法得到委托,但是我们需要操作多少属性呢? 显然这是一个无解的问题,我们只能为每个属性创建不同的委托。所以新的问题也随之产生: 我们如何保存那些委托?如何让它们与属性关联起来? Dictionary或者Hashtable或许是较好的选择(.net 3.5),然而,这些对象内部的数据结构在查找时,并不是零成本, 它们会消耗优化的大部分成果。 另外,在实现缓存委托的问题上,并发问题也是值得我们考虑的,不高效的并发设计还会让优化的成果继续丢失!

所以,我认为优化反射是个复杂问题,至少有3个环节是需要考虑的:
1. 如何得到委托?
2. 如何缓存委托?
3. 如何支持并发?

得到委托是容易的,但它只是一个开始!





回到顶部

招聘信息

公司需要若干名 .net 方面的高级开发人员,要求熟悉以下技术领域:
1. .net framework
2. ASP.NET
3. SQL SERVER (T-SQL, SP)
4. JavaScript, jQuery
5. CSS
6. 常见的设计模式。

说明:
1. 公司名称: 明源软件  
2. 工作地点:武汉。
3.  我只是负责【推荐】,具体细节请发邮件给我:liqifeng0503@163.com  
4. 咨询招聘相关的疑问也请发邮件给我,评论中的疑问一律不回复!

博客中所有代码将在后续博客中给出。

如果,您认为阅读这篇博客让您有些收获,不妨点击一下右下角的 【 推荐 】按钮。
如果,您希望更容易地发现我的新博客,不妨点击一下右下角的 【 关注 Fish Li 】。
因为,我的写作热情也离不开您的肯定支持。

感谢您的阅读,如果您对我的博客所讲述的内容有兴趣,请继续关注我的后续博客,我是Fish Li 。

 

 

分类:  C#基础

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于优化反射性能的总结(上)的详细内容...

  阅读:58次