優化特性(Attribute)性能

經過這篇文章,不只能夠了解到Attribute的工做原理,還能夠了解到GetcustomeAttribute是的內部執行流程。最後,你會看到,使用緩存機制能夠極大的優化反射Attribute的性能。數據庫

 

本文結構:緩存

  1.爲何在對象上標記Attribute性能很慢。性能優化

  2.編譯器如何編譯帶有Attribute標記的類型ide

  3.定義解析器,在運行時獲取並解析對象上的Attribute函數

  4.GetCustomeAttributes方法的工做原理性能

  5.優化Attribute優化

  6.Attribute性能優化完整代碼this

  7.總結spa

 

參考資料:線程

關於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詳細說明:

 

2 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性能優化完整代碼

 [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。

相關文章
相關標籤/搜索