Attribute操作的效能最佳化方式

來源:互聯網
上載者:User

標籤:

Attribute是.NET平台上提供的一種元編程能力,可以通過標記的方式來修飾各種成員。無論是組件設計,語言之間互連,還是最普通的架構使用,現在已經都離不開Attribute了。迫於Attribute的功能的重要性(Kent Beck認為NUnit比早期JUnit設計的好,一個主要方面便是利用了Attribute),Java語言也在5.0版本中引入了與Attribute類似的Annotation概念。不過Attribute說到底也是一種反射操作,平時正常使用不會帶來問題,但是密集的調用還是對效能有一定影響的。這次我們就來總結看看我們究竟可以如何迴避Attribute操作的一些效能問題。

假設我們有一個Attribute,它定義在一個類型上:

[AttributeUsage(AttributeTargets.Class,    AllowMultiple = true,    Inherited = true)]public class TestAttribute : Attribute{    public TestAttribute(string prop)    {        this.Prop = prop;    }    public TestAttribute() { }    public string Prop { get; set; }}[Test("Hello World")][Test(Prop = "Hello World")]public class SomeClass { }

那麼,如果我們需要獲得SomeClass類型上所標記的TestAttribute,我們一般會使用Type對象的GetCustomAttributes方法。那麼在其中又發生了什麼呢?

通過.NET Reflector來追蹤其中實現,會發現這些邏輯最終是由CustomAttribute的GetCustomAttributes方法完成的,感興趣的朋友們可以找到那個最複雜的重載。由於實現有些複雜,我沒有看懂完整的邏輯,但從關鍵的代碼上可以看出,它其實是使用了Activator.CreateInstance方法建立對象,並且使用反射對Attribute對象的屬性進行設定。於是我便打算瞭解一下這些反射操作占整個GetCustomAttributes方法的多大比重:

CodeTimer.Time("GetCustomAttributes", 1000 * 100, () =>{    var attributes = typeof(SomeClass).GetCustomAttributes(typeof(TestAttribute), true);});CodeTimer.Time("Reflection", 1000 * 100, () =>{    var a1 = (TestAttribute)Activator.CreateInstance(typeof(TestAttribute), "Hello World");    var a2 = (TestAttribute)Activator.CreateInstance(typeof(TestAttribute));    typeof(TestAttribute).GetProperty("Prop").SetValue(a2, "Hello World", null);});

結果如下:

GetCustomAttributes        Time Elapsed:   2,091ms        CPU Cycles:     5,032,765,488        Gen 0:          43        Gen 1:          0        Gen 2:          0Reflection        Time Elapsed:   527ms        CPU Cycles:     1,269,399,624        Gen 0:          40        Gen 1:          0        Gen 2:          0

可以看出,雖然GetCustomAttributes方法中使用了反射進行對象的建立和屬性設定,但是它的大部分開銷還是用於擷取一些中繼資料的,它們佔據了3/4的時間,而反射的開銷其實只佔了1/4左右。這就有些令人奇怪了,既然是靜態中繼資料,為什麼.NET Framework不對這些資料進行緩衝,而是每次再去取一次呢?即便是我們不應該緩衝最後得到的Attribute對象,但是用於構造對象的“資訊”是完全可以緩衝下來的。

事實上.NET Framework事實上已經給出了足夠的資訊,那便是CustomAttributeData的GetCustomAttributes方法,它返回的是IList<CustomAttributeData>對象,其中包含了構造Attribute所需要的全部資訊。換句話說,我完全可以根據一個CustomAttributeData來“快速構建”Attribute對象:

public class AttributeFactory{    public AttributeFactory(CustomAttributeData data)    {        this.Data = data;        var ctorInvoker = new ConstructorInvoker(data.Constructor);        var ctorArgs = data.ConstructorArguments.Select(a => a.Value).ToArray();        this.m_attributeCreator = () => ctorInvoker.Invoke(ctorArgs);        this.m_propertySetters = new List<Action<object>>();        foreach (var arg in data.NamedArguments)        {            var property = (PropertyInfo)arg.MemberInfo;            var propertyAccessor = new PropertyAccessor(property);            var value = arg.TypedValue.Value;            this.m_propertySetters.Add(o => propertyAccessor.SetValue(o, value));        }    }    public CustomAttributeData Data { get; private set; }    private Func<object> m_attributeCreator;    private List<Action<object>> m_propertySetters;    public Attribute Create()    {        var attribute = this.m_attributeCreator();        foreach (var setter in this.m_propertySetters)        {            setter(attribute);        }        return (Attribute)attribute;    }}

AttributeFactory利用了FastReflectionLib,將ConstructorInfo和PropertyInfo封裝成效能很高的ConstructorInvoker和PropertyAccessor對象,這樣使用起來便有數量級的效能提高。我們再來進行一番測試:

var factories = CustomAttributeData.GetCustomAttributes(typeof(SomeClass))    .Where(d => d.Constructor.DeclaringType == typeof(TestAttribute))    .Select(d => new AttributeFactory(d)).ToList();CodeTimer.Time("GetCustomAttributes", 1000 * 100, () =>{    var attributes = typeof(SomeClass).GetCustomAttributes(typeof(TestAttribute), true);});CodeTimer.Time("AttributeFactory", 1000 * 100, () => factories.ForEach(f => f.Create()));

結果如下:

GetCustomAttributes        Time Elapsed:   2,131ms        CPU Cycles:     5,136,848,904        Gen 0:          43        Gen 1:          43        Gen 2:          0Attribute Factory        Time Elapsed:   18ms        CPU Cycles:     44,235,564        Gen 0:          4        Gen 1:          4        Gen 2:          0

在這裡,我們先獲得SomeClass中所有定義過的CustomAttributeData對象,然後根據其Constructor的類型來判斷哪些是用於構造TestAttribute對象的,然後用它們來構造AttributeFactory。在實際使用過程中,AttributeFactory執行個體可以緩衝下來,並反覆使用。這樣的話,我們即可以每次得到新的Attribute對象,又可以避免GetCustomAttributes方法所帶來的莫名其妙的開銷。

事實上,我們完全可以利用這個方法,來實現一個效能更高的GetCustomAttributesEx方法,它的行為可以和.NET內建的GetCustomAttributes完全一致,但是效能可以快上無數——可能是100倍。不過,這個方法雖然不難編寫,但比較麻煩。因為CustomAttributeData只能用於獲得“直接定義”在某個成員上的資料,而實際情況是,我們往往還必鬚根據某個Attribute上標記的AttributeUsage的AllowMultiple和Inherited屬性來決定是否要遍曆整個繼承鏈。只有這般,我們才能百分之百地重現GetCustomAttribute方法的行為。

不過我們在這裡有個優勢,那便是“靜態”。一旦“靜態”,我們便可以為某個特定的情境,用“肉眼”判斷出特定的處理方式,這樣便不需要一個非常通用的GetCustomAttributeEx方法了。例如在實際使用過程中,我們可以可以發現某個Attribute的Inherited屬性為false,那麼我們便可以免去遍曆繼承鏈的麻煩。

最後還有兩點可能值得一提:

除了Type,Assembly等成員內建的GetCustomAttributes方法之外,Attribute類也有些靜態GetCustomAttributes方法可用於擷取Attribute對象。但是,通過.NET Reflector,我們可以發現,Attribute類中的靜態方法,最終還是委託給各自的執行個體方法,因此不會有效能提高。唯一區別對待的是ParameterInfo——不過我沒搞懂為什麼那麼複雜,感興趣的朋友可以自行探索一番。

如果僅僅是判斷一個成員是否定義了某個特定類型的Attribute對象,那麼可以使用Attribute.IsDefined靜態方法。它的效能比GetCustomAttributes後再判斷數組的Length要高效許多倍。不過個人認為這點倒並不是非常重要,因為這原本就是個靜態資訊,即便是我們使用較慢的GetCustomAttributes方法來進行判斷,也可以把最終的true或false結果進行緩衝,這自然也不會有效能問題了。

我們之所以要反覆調用GetCustomAttributes方法,就是因為每次得到的Attribute對象都是建立的,因此在某些情境下可能無法緩衝它們。不過現在已經有了現在更快的做法,在這方面自然也就不會有太大問題了。

Attribute操作的效能最佳化方式

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.