1. 程式人生 > >原 .NET/C# 反射的的效能資料,以及高效能開發建議(反射獲取 Attribute 和反射呼叫方法)

原 .NET/C# 反射的的效能資料,以及高效能開發建議(反射獲取 Attribute 和反射呼叫方法)

  大家都說反射耗效能,但是到底有多耗效能,哪些反射方法更耗效能;這些問題卻沒有統一的描述。
  
  本文將用資料說明反射各個方法和替代方法的效能差異,並提供一些反射程式碼的編寫建議。為了解決反射的效能問題,你可以遵循本文采用的各種方案。
  
  本文內容
  
  反射各方法的效能資料
  
  反射的高效能開發建議
  
  建立型別的例項
  
  反射獲取 Attribute
  
  反射呼叫公共 / 私有方法
  
  使用預編譯框架
  
  附本文效能測試所用的程式碼
  
  所有反射相關方法
  
  IsDefined 和 GetCustomAttribute 的專項比較
  
  參考資料
  
  反射各方法的效能資料
  
  我使用 BenchmarkDotNet 基準效能測試來評估反射各個方法的效能。測試的程式基於 .NET Core 2.1 開發。
  
  先直觀地貼出我的執行結果:
  
  ▲ 各反射不同方法的執行基準測試結果
  
  我把上面的表格複製下來成為文字,這樣你也可以拿走我的這部分資料:
  
  Method Mean Error StdDev Median
  
  Assembly 13.5315 ns 0.3004 ns 0.4764 ns 13.4878 ns
  
  Attributes 7.0893 ns 0.1248 ns 0.1168 ns 7.0982 ns
  
  CustomAttributes 1,489.1654 ns 29.4428 ns 27.5408 ns 1,482.5038 ns
  
  GetCustomAttributesData 1,514.5503 ns 29.6863 ns 39.6303 ns 1,507.2949 ns
  
  GetCustomAttributes 1,171.8969 ns 22.5305 ns 27.6695 ns 1,167.2777 ns
  
  GetCustomAttribute 1,139.8609 ns 22.8043 ns 24.4003 ns 1,140.5437 ns
  
  GetCustomAttribute_Generic 1,115.0049 ns 13.1929 ns 11.6952 ns 1,111.4426 ns
  
  GetCustomAttributes_Generic 1,164.5132 ns 22.7775 ns 24.3716 ns 1,165.2747 ns
  
  New 0.0003 ns 0.0013 ns 0.0012 ns 0.0000 ns
  
  Lambda 0.0063 ns 0.0149 ns 0.0139 ns 0.0000 ns
  
  Activator_CreateInstance 48.8633 ns 0.6300 ns 0.5893 ns 48.8906 ns
  
  Activator_CreateInstance_Generic 47.7029 ns 0.9649 ns 1.0724 ns 47.5851 ns
  
  Expression_New 75,634.4035 ns 1,467.3285 ns 1,372.5400 ns 75,413.2837 ns
  
  CachedExpression_New 7.8923 ns 0.1988 ns 0.4105 ns 7.7004 ns
  
  如果你希望瞭解以上每一項的意思,可以通過閱讀本文文末的程式碼來了解其實現。基本上名稱就代表著反射呼叫相同的方法。
  
  你一定會說這張表不容易看出效能差距。那麼我一定會放圖:
  
  那個 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 的效能差距也有些大:
  
  Method Mean Error StdDev Ratio RatioSD
  
  IsDefined 653.8 ns 13.07 ns 16.53 ns 1.00 0.00
  
  GetCustomAttribute 1,149.6 ns 22.97 ns 22.56 ns 1.76 0.06
  
  GetGenericCustomAttribute 1,216.5 ns 24.15 ns 54.51 ns 1.81 0.07
  
  咋看之下似乎與 GetCustomAttribute 方法重複,而且如果先判斷再獲取,可能總時間更長。不過這種方法就是適用於一次性對大量型別進行判斷,如果只有少量型別定義了某種 Attribute,那麼提前使用 IsDefined 判斷可以獲得總體更加的效能。
  
  反射的高效能開發建議
  
  建立型別的例項
  
  如果你能訪問到型別:
  
  建議直接使用 new,效能最好。
  
  如果不希望直接 new 出來,可以考慮使用 Func 或者 Lazy 建立。這時會多消耗一些效能,不過基數小,增量不大。
  
  如果你不能訪問到型別:
  
  如果只能從 Type 建立,則使用 Activator.CreateInstance 系列。
  
  如果你使用其他方式建立,請一定使用快取。
  
  除了使用 Expression 建立,你還可以使用 Emit 建立,不過這也要求能夠訪問到型別:
  
  使用 Emit 生成 IL 程式碼 - 呂毅
  
  對於快取,可以參考:
  
  .NET Core/Framework 建立委託以大幅度提高反射呼叫的效能 - 呂毅
  
  .NET/C# 推薦一個我設計的快取型別(適合快取反射等耗效能的操作,附用法) - 呂毅
  
  對於建立物件更多的效能資料,可以參考:
  
  C# 直接建立多個類和使用反射建立類的效能 - 林德熙
  
  C# 效能分析 反射 VS 配置檔案 VS 預編譯 - 林德熙
  
  反射獲取 Attribute
  
  獲取 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>();
  
  1
  
  你可以閱讀 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 =www.meiwanyule.cn Expression.New(typeof(ReflectionTarget));
  
  var lambda =www.mhylpt.com Expression.Lambda<Func<ReflectionTarget>>(@new).Compile();
  
  _cachedExpressionFunc = lambda;
  
  }
  
  return _www.dasheng178.com cachedExpressionFunc;
  
  }
  
  }
  
  [Benchmark]
  
  public void Assembly()
  
  {
  
  var assembly =www.mcyllpt.com _targetType.Assembly;
  
  }
  
  [Benchmark]
  
  public void Attributes()
  
  {
  
  var attributes = _targetType.Attributes;
  
  }
  
  [Benchmark]
  
  public void CustomAttributes(www.hjylp178.com)
  
  {
  
  var attribute = _targetType.CustomAttributes.FirstOrDefault(
  
  x => x.AttributeType www.ysyl157.com== 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 =www.yigouyule2.cn _targetType.GetCustomAttribute(typeof(ReflectionTargetAttribute), false);
  
  }
  
  [Benchmark]
  
  public void GetCustomAttribute_Generic(www.hengy178.com)
  
  {
  
  var attribute = _targetType.GetCustomAttribute<ReflectionTargetAttribute>(false);
  
  }
  
  [Benchmark]
  
  public void GetCustomAttributes_Generic()
  
  {
  
  var attribute = _targetType.GetCustomAttributes<ReflectionTargetAttribute>(false);
  
  }
  
  [Benchmark]
  
  public void New(www.120xh.cn)
  
  {
  
  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(
  
  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);
  
  參考資料
  
  c# - Is there a benefit of using IsDefined over GetCustomAttributes - Stack Overflow
  
  Accessing Attributes by Using Reflection (C#) - Microsoft Docs
  
  win10 uwp 反射
  
  Reference Source
  
  A Super-Fast C# Extension Method using Expression Trees to Create an instance from a Type
  
  Retrieving Custom Attributes Using Reflection - Scott Dorman
  
  Showtime - BenchmarkDotNet
  
  Choosing RunStrategy - BenchmarkDotNet