性能测试:Reflection VS CodeDom


    进行这个测试是由数据持久层的性能问题引起的。
    访问一个实体的属性,直接访问当然是最好的方式,但在数据持久层(包括其他一些工厂模式)中,持久层不能预知实体的属性,自然也无法用直接访问的方式来获取或设置属性值。在这种前提下,反射成为被广泛采用的访问方式。但众所周知,反射的性能是比较低的,大量的使用反射会造成程序性能的下降。.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 @ 2006-08-11 21:12  同一片海  阅读(13425)  评论(22编辑  收藏  举报