zoukankan      html  css  js  c++  java
  • 优化特性(Attribute)性能

    优化特性(Attribute)性能

    通过这篇文章,不仅可以了解到Attribute的工作原理,还可以了解到GetcustomeAttribute是的内部执行流程。最后,你会看到,使用缓存机制可以极大的优化反射Attribute的性能。

     

    本文结构:

      1.为什么在对象上标记Attribute性能很慢。

      2.编译器如何编译带有Attribute标记的类型

      3.定义解析器,在运行时获取并解析对象上的Attribute

      4.GetCustomeAttributes方法的工作原理

      5.优化Attribute

      6.Attribute性能优化完整代码

      7.总结

     

    参考资料:

    关于Attribute的缓存思想请参见后面的链接,当然这篇文章里还介绍了依赖注入以及ORM中的一些优化,链接如下:http://www.codeproject.com/Articles/503527/Reflection-optimization-techniques

    关于CustomeAttribute的介绍请参见ECMA-CLI文档第二部分Partition_II_Metadata的第22章第10节,但我再后面还是给出了这个介绍。

     

    1.为什么在对象上标记Attribute性能很慢。

     首先请看如下代码:

    复制代码
       [TableAttribute("student")]
    
        class StudentModel {
    
            public string Id{get;set;}
    
        }
    
     
    
        class TableAttribute : Attribute {
    
            string name;
    
     
    
            public string Name {
    
                get { return name; }
    
            }
    
            public TableAttribute(string name) {
    
                this.name = name;
    
            }
    
        }
    复制代码

    性能损耗1:每创建一个StudentModel对象,都要利用反射创建一个TableAttribute对象。

    性能损耗2:上一步骤中创建的所有TableAttribute都是完全相同的,值都是”student”。换句话说,如果创建了n 个StudentModel,那么就会具有n个完全一样的TableAttribute。

     

    2.编译器如何编译带有Attribute标记的类型

    编译器在编译上述代码时,发现StudentModel有一个TableAttribute特性,就会在最终的程序集中生成一个customeAttribute元数据表,这个表有三个字段:parent,type,value。其中parent指向StudentModel类,type指向TableAttribute的构造函数,value值是要传递给TableAttribute的参数,这个里的参数就是”student”。如下图:

    3.定义解析器,在运行时获取并解析对象上的Attribute

    如果对象上仅仅是标记了Attribute,那么是不会有什么性能损失的,因为我们并没有使用它。

    每当我们自定义一个Attribute时,都要同时创建一个用于解析这个Attribute的解析器。这个解析器需要做两件事:第一获取对象上标记的Attribute,其次根据Attribute中的属性值来执行相应的动作。代码如下:

    复制代码
     class AttributeInterpreter {
    
            public static void Interprete() {
    
                TableAttribute[] attributes = typeof(StudentModel).GetCustomAttributes(typeof(TableAttribute), true) as TableAttribute[];
    
                foreach(var eachAttribute in attributes) {
    
                    string tableName = eachAttribute.TableName;
    
                    //根据tableName访问数据库中的对应表,然后读取数据并创建StudentModel对象。
    
                }
    
            }
    
        }
    复制代码

    这里,首先获取StudentModel的Type,然后调用Type的GetCustomeAttributes方法,并传递了typeof(TableAttribute)参数。最后GetCustomeAttributes方法返回StudentModel上标记的TableAttribue对象。

    附注:ECMA-CLI中的CustomeAttribute详细说明:

     

     ECMA-CLI CustomeAttribute简介

    2 . 1 0 C us to mAt tr i b ut e : 0 x0 C
    The CustomAttribute table has the following columns:
    ? Parent (an index into any metadata table, except the CustomAttribute table itself; more precisely,
    a HasCustomAttribute (§24.2.6) coded index)
    ? Type (an index into the MethodDef or MemberRef table; more precisely, a CustomAttributeType
    (§24.2.6) coded index)
    ? Value (an index into the Blob heap)
    Partition II 119
    The CustomAttribute table stores data that can be used to instantiate a Custom Attribute (more precisely, an
    object of the specified Custom Attribute class) at runtime. The column called Type is slightly misleading—it
    actually indexes a constructor method—the owner of that constructor method is the Type of the Custom
    Attribute.
    A row in the CustomAttribute table for a parent is created by the .custom attribute, which gives the value of
    the Type column and optionally that of the Value column (§21).

    ECMA-CLI CustomeAttribute简介

    4.GetCustomeAttributes方法的工作原理

    在运行时,当调用GetCustomeAttributes时,CLR会遍历customeAttribute元数据表,查找parent=StudentModle和type=TableAttribute的CustomeAttribute元数据表。找到之后,根据type字段找到TableAttribute,然后创建他的实例,调用构造函数,并为构造函数传递参数“student”。

     

    5.优化Attribute

    优化的第一步就是利用缓存机制来存储已经创建好的TableAttribute,当需要对象时直接从缓存里取,这样只有在第一次创建StudentModel时,才会反射创建TableAttribute对象。如下

    复制代码
     private static Dictionary<Type, TableInfo> cache = new Dictionary<Type, TableInfo>();
            public StudentModel() {
                Type studentModelType = this.GetType();
                Type tableAttType=typeof(TableAttribute);
                if(!cache.TryGetValue(tableAttType, out tableINfo)) {
                    TableAttribute[] tableAttributes = studentModelType.GetCustomAttributes(typeof(TableAttribute), true) as TableAttribute[];
    
                    if(tableAttributes == null)
                        return;
    
                    TableAttribute tableAttribute = tableAttributes[0];
                    cache.Add(tableAttType, new TableInfo(tableAttribute.TableName));
                }
    复制代码

    使用缓存需要考虑锁的问题。当几个线程同时修改缓存时,必须保证在同一时刻只有一个线程修改,这里使用双锁机制来进行线程同步。如下:

    复制代码
     public StudentModel() {
                Type studentModelType = this.GetType();
                Type tableAttType=typeof(TableAttribute);
    
                TableInfo tableInfo;
                if(!cache.TryGetValue(tableAttType, out tableInfo)) {
                    lock(this) {
                        if(!cache.TryGetValue(tableAttType, out tableInfo)) {
                            TableAttribute[] tableAttributes = studentModelType.GetCustomAttributes(typeof(TableAttribute), true) as TableAttribute[];
    
                            if(tableAttributes == null)
                                return;
    
                            TableAttribute tableAttribute = tableAttributes[0];
                            cache.Add(tableAttType, new TableInfo(tableAttribute.TableName));
                        }
                    }
                }
            }
    复制代码

    优化的结果:只在第一次创建对象时反射Attribute,减少了内存的使用。

     

    6.Attribute性能优化完整代码

     Attribute性能优化完整代码

    [Table("student")]
    class StudentModel {
    public string Id{get;set;}
    //定义缓存,存储已经创建好的TableAttribute对象。
    private static Dictionary<Type, TableInfo> cache = new Dictionary<Type, TableInfo>();
    public StudentModel() {
    Type studentModelType = this.GetType();
    Type tableAttType=typeof(TableAttribute);

    TableInfo tableInfo;
    if(!cache.TryGetValue(tableAttType, out tableInfo)) {
    lock(this) {
    if(!cache.TryGetValue(tableAttType, out tableInfo)) {
    TableAttribute[] tableAttributes = studentModelType.GetCustomAttributes(typeof(TableAttribute), true) as TableAttribute[];

    if(tableAttributes == null)
    return;

    TableAttribute tableAttribute = tableAttributes[0];
    cache.Add(tableAttType, new TableInfo(tableAttribute.TableName));
    }
    }
    }
    }
    }
    //AlloMultiple为true,表明这个特性不能被重复标记,原因很简单,一个实体对象不能映射到两个表。
    [AttributeUsage(AttributeTargets.Class,AllowMultiple=false)]
    class TableAttribute : Attribute {
    string tableName;

    public string TableName {
    get { return tableName; }
    }
    public TableAttribute(string name) {
    this.tableName = name;
    }
    }

    Attribute性能优化完整代码

    7.总结

       在使用Attribute特性时,编译器会在最终的程序集中生成一个CustomeAttribute元数据表,这个表实际上是一个映射表,其中的type字段和parent字段将特性和对象相关联。

    仅仅定义个Attribute是没有任何实际作用的,我们需要定义一个解析器用于获取对象上的Attribute并根据其属性值采取相应的动作。

    在运行的时候,想要获取对象上的某特性时,需要调用Type的GetCustomeAttributes方法。这个方法在内部使用了反射来获取特性并实例化特性。

    由于反射很浪费性能,并且对于类型的每一个对象,与其关联的Attribute都是相同的,所以可以利用字典只保存其中的一个。由于反射很浪费性能,所以可以考虑将创建好的Attribute缓存起来。

    如果这个缓存需要被多个线程修改,需要使用锁来同步,这里为了提供性能,使用了双锁机制和Monitor。

  • 相关阅读:
    监视用户是保存用户编辑还是放弃参照编辑
    AutoCAD: 添加鼠标快捷键/鼠标右键
    C# List<T>集合布尔运算
    List<T>的用法详解
    天正的坑
    C#札记
    AUTOCAD2013 以上利用ACCORECONSOLE+ SCR后台批量清理图纸
    BaiduSitemap
    三一邮件群发
    Windows+IIS+Mysql+php安装
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3375181.html
Copyright © 2011-2022 走看看