ChinaDHF
学而不思则罔,思而不学则殆。
posts - 41,  comments - 210,  trackbacks - 4

    进行这个测试是由数据持久层的性能问题引起的。
    访问一个实体的属性,直接访问当然是最好的方式,但在数据持久层(包括其他一些工厂模式)中,持久层不能预知实体的属性,自然也无法用直接访问的方式来获取或设置属性值。在这种前提下,反射成为被广泛采用的访问方式。但众所周知,反射的性能是比较低的,大量的使用反射会造成程序性能的下降。.Net框架提供的CodeDOM 对象模型为我们提供了另一种访问实体属性的方式,通过使用CodeDom,可以创建可在项目间共享的可重用的样板源代码,实现对C++ 模板的模拟。
    用CodeDom动态编译在内存中生成我们根据实体定制的类后,执行效率是非常高的,但是为此需要付出的代价是:动态编译是一个非常消耗性能和内存的过程。与使用反射相比,哪一种方式更合算呢?以下是我对两种方式的简单测试:

1.测试内容
获取一个简单实体类的实例的属性值。
实体类代码:
public class Student
    
{
        
private int? id;
        
private string name;

        
public int? Id
        
{
            
get return id; }
            
set { id = value; }
        }


        
public string Name
        
{
            
get return name; }
            
set { name = value; }
        }

    }

2.测试过程
分别用三种方式(直接、反射、动态编译)获取实体实例的属性值100000次。
测试代码:
 public void GetValue(object obj)
        
{
            DateTime startTime 
= DateTime.Now;

            
for (int i = 0; i < 100000; i++)
            
{
                Student s 
= (Student)obj;
                
int? id = s.Id;
                
string name = s.Name;
            }


            DateTime endTime 
= DateTime.Now;

            Console.WriteLine(((TimeSpan)(endTime 
- startTime)).TotalMilliseconds);
        }


        
public void GetValueByReflection(object obj)
        
{
            DateTime startTime 
= DateTime.Now;

            PropertyInfo pId 
= obj.GetType().GetProperty("Id");
            PropertyInfo pName 
= obj.GetType().GetProperty("Name");
            
for (int i = 0; i < 100000; i++)
            
{
                
int? id = (int?)pId.GetValue(obj, null);
                
string name = (string)pName.GetValue(obj, null);
            }


            DateTime endTime 
= DateTime.Now;

            Console.WriteLine(((TimeSpan)(endTime 
- startTime)).TotalMilliseconds);
        }


        
public void GetValueByCodeDom(object obj)
        
{
            DateTime startTime 
= DateTime.Now;

            CodeDomProvider prov 
= new CSharpCodeProvider();
            CompilerParameters para 
= new CompilerParameters();
            para.ReferencedAssemblies.Add(
"Entity.dll");
            para.GenerateInMemory 
= true;
            
string code = 
                
"namespace Entity{"+
                
"public class StudentValue : EntityValue" +
                
"{" +
                
"   public override object GetPropertyValue(object obj,string propertyName)" +
                
"   {" +
                
"        Student s = (Student)obj;" +
                
"        switch (propertyName)" +
                
"        {" +
                
"        case \"Id\":" +
                
"            return s.Id;" +
                
"        case \"Name\":" +
                
"            return s.Name;" +
                
"        default:" +
                
"            return null;" +
                
"        }" +
                
"   }" +
                
"}}";
            CompilerResults cr 
= prov.CompileAssemblyFromSource(para, code);
            Assembly assembly 
= cr.CompiledAssembly;
            EntityValue sv 
= assembly.CreateInstance("Entity.StudentValue"as EntityValue;

            
for (int i = 0; i < 100000; i++)
            
{
                
int? id = (int?)sv.GetPropertyValue(obj, "Id");
                
string name = (string)sv.GetPropertyValue(obj, "Name");
            }

            DateTime endTime 
= DateTime.Now;

            Console.WriteLine(((TimeSpan)(endTime 
- startTime)).TotalMilliseconds);
        }

(注:为简化测试,代码中省略了获取属性名称、属性类型的步骤,这个步骤对于使用反射和CodeDom是相同的。)
在第三种方式中,为方便动态生成类的调用,让其从一个虚基类中继承而来。虚基类代码如下:
public abstract class EntityValue
    
{
        
public abstract object GetPropertyValue(object obj,string propertyName);
    }

即便是循环100000次,多数情况下也无法取得第一种方式(直接读取)所用时间,此处暂且忽略不计。以下是第二、三种方式的测试结果(在IDE环境中直接运行):
第一次:Refletion 2.859秒,CodeDom 0.438秒
第二次:Refletion 2.609秒,CodeDom 0.422秒
第二次:Refletion 2.703秒,CodeDom 0.391秒
从这个结果看,使用CodeDom比使用Reflection使性能有大幅度提高,但是我们需要考虑另外两个因素:
1.我们用100000次进行测试,但现实中是否会用到如此多的反射次数。由于使用CodeDom方式时,绝大多数性能消耗在动态编译过程中,如果完成编译后只是少量的使用,其性能将远远低于使用反射。
2.CodeDom方式需使用更多的内存空间用于缓存编译结果。
而且不仅如此,如果在Windows平台上直接运行以上编译好的执行文件,发现几乎是另一个测试结果:
第一次:Refletion 0.594秒,CodeDom 0.406秒
第二次:Refletion 0.563秒,CodeDom 0.391秒
第二次:Refletion 0.609秒,CodeDom 0.406秒
在Windows平台上直接运行时,反射性能有了大幅度的提高!以致于使用CodeDom已经几乎没有优势。

我不知道这个测试是否合理,也无从得知这个测试结果是否可靠。但测试结果着实让我对CodeDom在避免反射、提高性能方面难以选择,不知大家如何认为?

06.08.14补充:
非常感谢大家的关注,根据回复中的建议(改用Stopwatch计时、Ralease方式编译、分别测试CodeDom编译时间和访问时间),我于今天重新进行了测试,10次测试的平均结果为:
使用Reflection:0.530秒
使用CodeDom:动态编译 0.281秒,访问用时 0.107秒,总计用时 0.388秒
(由于换用了一台性能好一些的机器,平均速度比以上测试偏快)
posted on 2006-08-11 21:12 东海风 阅读(12447) 评论(22)  编辑 收藏

FeedBack:
2006-08-11 21:54 | idior      
不知道stopwatch吗?你这样测是不准的
  回复  引用  查看    
2006-08-11 22:21 | edison1024      
DateTime这种是测不准滴。
应该采用stopwatch,在 System::Diagnostics下
  回复  引用  查看    
#3楼 [楼主]
2006-08-11 22:41 | 东海风      
非常感谢 idior , edison1024 。
我改用Stopwatch后重新测试了一下,结果为:
IDE环境中运行:
第一次:Refletion 3.007秒,CodeDom 0.498秒
第二次:Refletion 2.989秒,CodeDom 0.454秒
第二次:Refletion 2.979秒,CodeDom 0.430秒
Windows中直接运行:
第一次:Refletion 0.575秒,CodeDom 0.417秒
第二次:Refletion 0.565秒,CodeDom 0.423秒
第二次:Refletion 0.528秒,CodeDom 0.436秒


  回复  引用  查看    
2006-08-11 22:52 | 海蓝      
@东海风
你的Build类型是选的Debug吗?如果是,VS的IDE要处理Debug信息,这可能会对结果有些影响
能否把你的完整的代码发布出来?我尝试把你的代码放到我的工程里,但运行时总是会报错
  回复  引用  查看    
#5楼 [楼主]
2006-08-11 23:03 | 东海风      
@海蓝
除了实例化一个Student(用作方法中的实参)及Main方法对以上三种方法的调用外,这已经是全部代码了。需要注意的是文中提到的那个虚基类,需要编译为一个Dll文件(Entity.dll)供动态生成类使用。
  回复  引用  查看    
#6楼 [楼主]
2006-08-11 23:26 | 东海风      
@海蓝
非常感谢你的提醒。在IDE中和Windows中执行反射性能之所以有这么大的差别,答案就在这儿了。我生成Release版本后重新测试,在IDE中执行与在Windows中直接执行结果基本一致了,就是以上在Windows中运行时的结果。
  回复  引用  查看    
2006-08-12 00:31 | 海蓝      
@东海风
如果两种办法的性能差异不大,我就推荐使用反射了。因为CodeDOM这种拼凑代码的方式会导致代码的阅读和维护很麻烦,而且不能在编译期进行语法检查,有错只能在运行期才暴露,这也增加了调试和排错的困难
不过msdn中关于CodeDOM的介绍中提到,在一些特定的场合,CodeDOM还是很有用的
  回复  引用  查看    
2006-08-12 00:47 | 路过 [未注册用户]
问个 菜鸟的问题

int? 是什么东西。。

谢谢。
  回复  引用  查看    
2006-08-12 01:33 | U2U      
学习中。,。。。
  回复  引用  查看    
2006-08-12 08:02 | Hi [未注册用户]
@路过
int? 是C#2.0中的新类型,即Nullable(可空)类型。

  回复  引用  查看    
2006-08-12 13:35 | yzx110      
在.net2.0种,反射的性能相比已经大幅度提高了,在.net2.0没有发布以前就有微软官方的测试出来过。
分别比较了:直接调用,Interface调用,反射调用,多态调用,好像不止这些,记得不全了。
反射性能很不错了。

CodeDOM这种方式我一直觉得是个比较不错的东西,但是一直没有遇到适用的场景。
  回复  引用  查看    
#12楼 [楼主]
2006-08-12 15:54 | 东海风      
@海蓝
是的。如果使用CodeDom没有明显的性能优势,就真的是“费力不讨好”了,还是用反射合算一些。
  回复  引用  查看    
#13楼 [楼主]
2006-08-12 16:05 | 东海风      
@yzx110
从这个测试看,只要不是滥用反射并尽量优化使用反射,反射的性能问题不会对应用程序的性能造成大的影响,大可不必“谈反射色变”,甚至把应用程序的性能问题全部归咎于反射。
从相对数看,反射性能的确较低,应该是直接调用的几十倍甚至上百倍。但从绝对数看,200000次反射耗时0.5秒左右,完全是可以接受的,这在整个应用程序(可能包括数据访问、业务处理、界面显示)中只占很小的比例,不会对应用程序性能造成明显影响。
  回复  引用  查看    
2006-08-12 21:00 | 达达      
路过顶一下
  回复  引用  查看    
2006-08-13 12:52 | skywood [未注册用户]
楼主要是有空的话用Emit再做一个试试
  回复  引用  查看    
2006-08-14 11:10 | henry      
830条记录获取测试情况,运行10次第一次和最后一次不要取其中8次的平均时间(查看方便只保留3位小数).
0.040(秒) CodeDom映射到实体集合
0.123(秒) .NET的反射功能的映射到实体集合(反射信息已缓存)
0.024(秒) 直接填充到DataSet

这些数据是我很早之前的测试结果。
现在使用是经过优化的,在此功能上CodeDom的效率和反射的效率大概有6-8倍的差距。
在.NET2。0中没有测试过。

  回复  引用  查看    
2006-08-14 13:41 | Colin Han      
关注一下。
建议能够将CodeDOM的性能作一些详细的分析,例如:动态编译用了多少时间?访问用了多少时间等?
然后具体根据实际情况,择优使用。
  回复  引用  查看    
#18楼 [楼主]
2006-08-14 15:15 | 东海风      
@henry
CodeDom方式下的耗时 0.040秒 包含动态编译时间吗?
  回复  引用  查看    
#19楼 [楼主]
2006-08-14 15:39 | 东海风      
@Colin Han
感谢关注,我已将测试结果补充在上文中。
  回复  引用  查看    
2006-08-14 15:44 | Colin Han      
@东海风

大大反应神速,感谢共享这么好的信息。
  回复  引用  查看    
2006-08-14 17:04 | henry      
@东海风
我的测试结果是完全整个数据获取操作。
在新版的组件中,这种查询填充对象操作已的效率已经优越于Fill DataSet.
以下是新的测试结果:
P4 1.7 1G
获取NorthWind.Orders表所有记录的结果(头和尾去丢)
测试代码
System.Collections.IList list = NorthWind.Entities.Orders.Mapper.List
结果
Record:830
Time:00.02713864
=================================================
Record:830
Time:00.02972976
=================================================
Record:830
Time:00.01973798
=================================================
Record:830
Time:00.02078476
=================================================
Record:830
Time:00.02262941
=================================================
Record:830
Time:00.02215533
=================================================
Record:830
Time:00.02194888
=================================================
Record:830
Time:00.02048417
=================================================
  回复  引用  查看    
2007-10-27 19:06 | cokkiy [未注册用户]
CodeDom不是这样用的,如果这样用,我觉得不如反射,既简单,性能也没多少差别。CodeDom的应用场景,应该是Create一次,查询很多次的场景。比如在动态查询中,管理员选择需要查询的字段(属性),组合生成一个新的类,编译后缓存到backstore,其他人员利用这个查询。如果不是这种场合,真的没必要用CodeDom。
  回复  引用  查看    

标题  
姓名  
主页
Email (只有博主才能看到) 
验证码 *  看不清,换一张
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2006-08-14 15:44 编辑过
 
另存  打印
最新IT新闻:
 
计数器:

阿里妈妈再掀疯狂采购风,网站广告位严重告急,急召天下站长


<2006年8月>
303112345
6789101112
13141516171819
20212223242526
272829303112
3456789

与我联系

常用链接

留言簿(2)

随笔档案(39)

收藏夹(12)

技术网站

阅读排行榜