.NET/C# 反射的的性能數據,以及高性能開發建議(反射獲取 Attribute 和反射調用方法)——轉載

原文連接: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 中的一些屬性,例如 AssemblyAttributes 也是比較「耗時」的;固然,這是納秒級別,你能夠將它忽略。
要不要試試把第四梯隊的也幹掉呢?因而你能夠獲得 newLambda 的差別:

本來在上面全部圖中看起來都沒有時間的 newLambda 居然差別如此巨大;不過,這都是千分之一納秒級別了;若是你建立的類數量不是百萬級別以上,你還真的能夠忽略。
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 也是耗時的操做。
若是你只是獲取極少數類型的 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);
        }
    }
}
相關文章
相關標籤/搜索