C#反射,效能優化,不止於優化

語言: CN / TW / HK

  架構師的價值,在於獨立且理性的思考

想要寫出靈活而且具有更好適應性的程式碼,反射是首選方案。

反射賦予程式在執行時動態建立例項的能力,可以在程式執行時(而非編譯時)獲取例項型別,獲取元資料資訊,動態呼叫例項方法及屬性,實現在通常程式設計邏輯中無法完成的功能,是程式設計體系中的高階技能。

反射的一大弊端是效能偏低,但反射效能究竟低多少,想必並非每個開發人員都瞭解,那麼本著嚴謹求實的精神,我們來分析一下反射的執行效率及其優化方案。

01

優化方案

首先說一下反射的優化方案,反射效能優化有以下幾種方案:

  1. 委託

  2. ILEmit(直接編寫IL,複雜度較高)

  3. 表示式樹(Expression,複雜度相對較高)

  4. 元資料快取

02

效能 測試

接下來,通過對一個屬性的讀寫操作,分析這幾種方案的效能。

下圖是分別採用:屬性、委託、ILEmit、表示式樹、元資料快取、反射,六種方案,對一個屬性進行讀寫操作的效能測試結果。

從測試結果可以看出:

通過反射讀取屬性值, 與直接通過屬性讀取,耗時相差283倍

通過反射寫入屬性值, 與直接通過屬性寫入,耗時相差77倍

相對來說,反射的效能,確實差了不少。

再從絕對耗時角度看,測試結果中,操作耗時的單位是ns(納秒),納秒和秒的換算關係如下:

1s 
= 1000 ms(毫秒)
= 1000000 us(微秒)
= 1000000000 ns(納秒)

取耗時最長的106 ns,相當於0.000000106 s,0.000106 ms,也就是說,即使在一個業務邏輯中,呼叫10000次反射設定屬性操作,總耗時也只有1.06 ms,通常一個邏輯操作耗時低於10 ms時,效能優化的必要性不大(一家之言,僅供參考)。

對於效能的追求,是每個技術人應當銘記於心的準則,可以根據自己的技術能力,業務場景以及任務排期,選擇採用不同的優化方案。

03

方案分析

分析一下這幾種方案:

  1. 委託

對比優化方案可知,委託的效能最好,讀效能提高42倍,寫效能提高41倍。

委託的實現難度較低,並且程式碼可閱讀性較好,是首選優化方案。

注意:這裡的委託是指delegate(包括:Action,Func),而不是Delegate,直接使用Delegate的效能比反射還要低2倍,需要將Delegate轉換為特定型別的delegate才能起到效能提升的作用。由此也帶來了委託的缺點,通用性不強,需要根據不同型別,建立相應的委託。

  1. ILEmit

ILEmit相當於直接通過IL編寫程式碼,建立過程相對複雜,程式碼可讀性低,編寫難度高,有興趣的可以研究一下這個庫 Sigil

  1. 表示式樹

直接編寫表示式樹也有一定難度,但相對於ILEmit要簡單很多,可以深入瞭解一下。對於屬性的讀寫操作,可以參考 FastProperty

  1. 元資料快取

元資料快取是最簡單的方案,在沒有精力採用其他方案時,是個不錯的選擇,能得到30%的效能提升。

重要說明 :此效能測試耗時只包含了不同方案下,屬性讀寫操作方法的耗時,而不包含生成委託、構造ILEmit、構造表示式樹的時間,如果算上這部分時間,那麼這幾種方案比直接使用PropertyInfo還要慢一倍,這一點一定要注意。這就給我們的優化方案提出了新的挑戰, 需要提前構建委託、Emit或表示式樹,並進行快取,以便後續操作使用,這樣才能達到效能優化的目的

04

總結

我們常說: 手裡拿個錘子,看什麼都像釘子。

究其原因是因為我們手裡只有錘子,瞭解更多的方案,擴大自己的知識邊界,讓自己的工具箱中多幾件工具,那麼面對不同問題場景時,便可以多幾種可選的應對方案。