原文連接:https://blog.walterlv.com/post/dotnet-high-performance-reflection-suggestions.html
*****
你們都說反射耗性能,可是到底有多耗性能,哪些反射方法更耗性能;這些問題卻沒有統一的描述。
本文將用數聽說明反射各個方法和替代方法的性能差別,並提供一些反射代碼的編寫建議。爲了解決反射的性能問題,你能夠遵循本文采用的各類方案。
*****html
反射各方法的性能數據
反射的高性能開發建議
建立類型的實例
反射獲取 Attribute
反射調用公共 / 私有方法
使用預編譯框架
附本文性能測試所用的代碼
全部反射相關方法
IsDefined 和 GetCustomAttribute 的專項比較c#
我使用 BenchmarkDotNet 基準性能測試來評估反射各個方法的性能。測試的程序基於 .NET Core 2.1 開發。
先直觀地貼出個人運行結果:
▲ 各反射不一樣方法的運行基準測試結果
我把上面的表格複製下來成爲文字,這樣你也能夠拿走個人這部分數據:
若是你但願瞭解以上每一項的意思,能夠經過閱讀本文文末的代碼來了解其實現。基本上名稱就表明着反射調用相同的方法。
你必定會說這張表不容易看出性能差距。那麼我必定會放圖:
性能差別圖 1緩存
那個Expression_New
在圖中獨樹一幟,遠遠把其餘方法甩在了後面。那是個什麼方法?
那是在使用 Expression
表達式建立一個類型的新實例:框架
var @new = Expression.New(typeof(ReflectionTarget)); var lambda = Expression.Lambda<Func<ReflectionTarget>>(@new).Compile(); var instance = lambda.Invoke();
也就是說,若是你只是但願建立一個類型的新實例,就不要考慮使用 Expression.New 的方式了。除非此方法將執行很是屢次,而你把那個 lambda 表達式緩存下來了。這對應着圖表中的 CachedExpression_New。
其餘的如今都看不出來性能差別,因而咱們把耗時最長的 Expression_New 一項去掉:
咱們馬上能夠從圖中獲得第二梯隊的性能巨頭 —— 就是 CustomAttributes
系列。我使用了多種不一樣的 CustomAttribute
獲取方法,獲得的結果差別不大,都「比較耗時」。不過在這些耗時的方法裏面找到不那麼耗時的,就是 Type
的擴展方法系列 GetCustomAttribute
了,比原生非擴展方法的性能稍好。
不過其餘的性能差別又被淹沒了。因而咱們把 CustomAttributes
系列也刪掉:
因而咱們又獲得了第三梯隊的性能大頭 —— Activator.CreateInstance
系列。而是否調用泛型方法的耗時差別不大。
而後,咱們把 Activator.CreateInstance 也幹掉,能夠獲得剩下其餘的性能消耗。
也就是說,只是獲取 Type
中的一些屬性,例如 Assembly
和 Attributes
也是比較「耗時」的;固然,這是納秒級別,你能夠將它忽略。
要不要試試把第四梯隊的也幹掉呢?因而你能夠獲得 new
和 Lambda
的差別:
本來在上面全部圖中看起來都沒有時間的 new
和 Lambda
居然差別如此巨大;不過,這都是千分之一納秒級別了;若是你建立的類數量不是百萬級別以上,你還真的能夠忽略。
而 new
指的是 new Foo()
,Lambda
指的是 var func = () => new Foo(); func();
。
對於 GetCustomAttribute
,還有另外一個方法值得注意:IsDefined
;能夠用來判斷是否認義了某個特定的 Attribute
。函數
var isDefined = _targetType.IsDefined(typeof(ReflectionTargetAttribute), false); if (isDefined) { var attribute = _targetType.GetCustomAttribute<ReflectionTargetAttribute>(); }
而這個方法與 GetCustomAttribute
的性能差距也有些大:
咋看之下彷佛與 GetCustomAttribute
方法重複,並且若是先判斷再獲取,可能總時間更長。不過這種方法就是適用於一次性對大量類型進行判斷,若是隻有少許類型定義了某種Attribute
,那麼提早使用 IsDefined
判斷能夠得到整體更加的性能。post
若是你能訪問到類型:
建議直接使用 new
,性能最好。
若是不但願直接new
出來,能夠考慮使用Func
或者Lazy
建立。這時會多消耗一些性能,不過基數小,增量不大。
若是你不能訪問到類型:
若是隻能從 Type
建立,則使用 Activator.CreateInstance
系列。
若是你使用其餘方式建立,請必定使用緩存。
除了使用 Expression
建立,你還可使用 Emit
建立,不過這也要求可以訪問到類型:
使用 Emit 生成 IL 代碼 - 呂毅
對於緩存,能夠參考:
.NET Core/Framework 建立委託以大幅度提升反射調用的性能 - 呂毅
.NET/C# 推薦一個我設計的緩存類型(適合緩存反射等耗性能的操做,附用法) - 呂毅
對於建立對象更多的性能數據,能夠參考:
C# 直接建立多個類和使用反射建立類的性能 - 林德熙
C# 性能分析 反射 VS 配置文件 VS 預編譯 - 林德熙性能
獲取 Attribute 也是耗時的操做。
若是你只是獲取極少數類型的 Attribute
,建議直接調用 GetCustomAttribute
擴展方法。
若是你須要判斷大量類型的 Attribute
,建議先使用 IsDefined
判斷是否存在,若是存在才使用 GetCustomAttribute
方法獲取真實實例。
反射調用公共 / 私有方法
反射調用方法與構造方法幾乎是同樣的,不一樣之處就在於公共方法能夠建立出委託緩存,而私有方法卻不行。
有了委託緩存,你只有第一次才須要真的調用反射,後續可使用緩存的委託或 Lambda 表達式;而私有方法是沒法建立的,你每次都須要經過反射來調用相關方法。
關於私有方法的反射:
C# 使用反射獲取私有屬性的方法
C# 反射調用私有事件
關於緩存:
.NET Core/Framework 建立委託以大幅度提升反射調用的性能 - 呂毅
.NET/C# 推薦一個我設計的緩存類型(適合緩存反射等耗性能的操做,附用法) - 呂毅
使用預編譯框架
使用預編譯框架,你能夠在編譯期間將那些耗時的反射操做編譯成相似 new 和屬性 get 這樣的簡單 CLR 調用,性能差距近乎於最開始圖表中第二張圖和第五張圖那樣,具備數千倍的差距。
課程 預編譯框架,開發高性能應用 - 微軟技術暨生態大會 2018 - walterlv
dotnet-campus/SourceFusion: SourceFusion is a pre-compile framework based on Roslyn. It helps you to build high-performance .NET code.
附本文性能測試所用的代碼
本文性能測試使用 BenchmarkDotNet,在 Main 函數中調用如下代碼跑起來:
BenchmarkRunner.Run<Reflections>();
你能夠閱讀 C# 標準性能測試 - 林德熙 瞭解基準性能測試的基本用法,在 C# 標準性能測試高級用法 - 林德熙 中瞭解到更多基準測試方法的使用。
全部反射相關方法測試
using BenchmarkDotNet.Attributes; using System; using System.Linq; using System.Linq.Expressions; using System.Reflection; namespace Walterlv.Demo.Reflection { public class Reflections { private static readonly Type _targetType = typeof(ReflectionTarget); private static Func<ReflectionTarget> _cachedExpressionFunc; private static Func<ReflectionTarget> CachedExpressionFunc { get { if (_cachedExpressionFunc == null) { var @new = Expression.New(typeof(ReflectionTarget)); var lambda = Expression.Lambda<Func<ReflectionTarget>>(@new).Compile(); _cachedExpressionFunc = lambda; } return _cachedExpressionFunc; } } [Benchmark] public void Assembly() { var assembly = _targetType.Assembly; } [Benchmark] public void Attributes() { var attributes = _targetType.Attributes; } [Benchmark] public void CustomAttributes() { var attribute = _targetType.CustomAttributes.FirstOrDefault( x => x.AttributeType == typeof(ReflectionTargetAttribute)); } [Benchmark] public void GetCustomAttributesData() { var attribute = _targetType.GetCustomAttributesData().FirstOrDefault( x => x.AttributeType == typeof(ReflectionTargetAttribute)); } [Benchmark] public void GetCustomAttributes() { var attribute = _targetType.GetCustomAttributes(typeof(ReflectionTargetAttribute), false).FirstOrDefault(); } [Benchmark] public void GetCustomAttribute() { var attribute = _targetType.GetCustomAttribute(typeof(ReflectionTargetAttribute), false); } [Benchmark] public void GetCustomAttribute_Generic() { var attribute = _targetType.GetCustomAttribute<ReflectionTargetAttribute>(false); } [Benchmark] public void GetCustomAttributes_Generic() { var attribute = _targetType.GetCustomAttributes<ReflectionTargetAttribute>(false); } [Benchmark] public void New() { var instance = new ReflectionTarget(); } [Benchmark] public void Lambda() { var instance = new ReflectionTarget(); } [Benchmark] public void Activator_CreateInstance() { var instance = (ReflectionTarget) Activator.CreateInstance(_targetType); } [Benchmark] public void Activator_CreateInstance_Generic() { var instance = Activator.CreateInstance<ReflectionTarget>(); } [Benchmark] public void Expression_New() { var @new = Expression.New(typeof(ReflectionTarget)); var lambda = Expression.Lambda<Func<ReflectionTarget>>(@new).Compile(); var instance = lambda.Invoke(); } [Benchmark] public void CachedExpression_New() { var instance = CachedExpressionFunc.Invoke(); } } } IsDefined 和 GetCustomAttribute 的專項比較 using System; using System.Reflection; using BenchmarkDotNet.Attributes; namespace Walterlv.Demo.Reflection { public class IsDefinedVsGetCustomAttribute { private static readonly Type _targetType = typeof(ReflectionTarget); [Benchmark(Baseline = true)] public void IsDefined() { var isDefined = _targetType.IsDefined(typeof(ReflectionTargetAttribute), false); } [Benchmark] public void GetCustomAttribute() { var attribute = _targetType.GetCustomAttribute(typeof(ReflectionTargetAttribute), false); } [Benchmark] public void GetGenericCustomAttribute() { var attribute = _targetType.GetCustomAttribute<ReflectionTargetAttribute>(false); } } }