1. 程式人生 > 其它 >dotnet 5 靜態欄位和屬性的反射獲取 沒有想象中那麼傷效能

dotnet 5 靜態欄位和屬性的反射獲取 沒有想象中那麼傷效能

技術標籤:c#C#dotnetWPF

在最近在做 WPF 框架開發的時候,看到了在 WPF 的 StaticExtension 裡面,有部分邏輯採用了反射的方法去獲取靜態欄位和靜態屬性。此時我第一個反應就是這部分邏輯的效能有鍋,於是嘗試了進行加上快取來優化。但是在使用了 Benchmark 進行效能測試的時候發現了,其實加上了快取的效能反而更差,也就是說在 dotnet 5 裡面的反射獲取靜態欄位和屬性的效能沒有想象的傷效能

本文並非說反射獲取靜態欄位和屬性不傷效能,而是指在本文約定的情況下,沒有那麼傷效能。本文完全依靠效能測試來說明

換句話說,不要在外面說德熙這個逗比說反射獲取靜態欄位和屬性不傷效能哈。如果要說句話,還請加上一大堆條件

在原本的 WPF 框架裡面有以下的邏輯,用來獲取靜態欄位或屬性

        private bool GetFieldOrPropertyValue(Type type, string name, out object value)
        {
            FieldInfo field = null;
            Type temp = type;

            do
            {
                field = temp.GetField(name, BindingFlags.Public | BindingFlags.
Static); if (field != null) { value = field.GetValue(null); return true; } temp = temp.BaseType; } while (temp != null); PropertyInfo prop = null; temp = type;
do { prop = temp.GetProperty(name, BindingFlags.Public | BindingFlags.Static); if (prop != null) { value = prop.GetValue(null, null); return true; } temp = temp.BaseType; } while (temp != null); value = null; return false; }

此時我期望不需要每次都通過 GetField 或 GetProperty 方法去獲取欄位或屬性的 FieldInfo 或 PropertyInfo 物件,再通過這些物件去獲取實際的值,甚至我都想要作出快取,通過 Func<object> 的方法返回靜態屬性或欄位

但是實際測試發現了其實嘗試省去 通過 GetField 或 GetProperty 方法去獲取欄位或屬性的 FieldInfo 或 PropertyInfo 物件,將 FieldInfo 或 PropertyInfo 物件快取起來,甚至通過 Func<object> 的方法返回靜態屬性或欄位的效能,其實都和沒有提升,甚至還因為構建字典的 Key 而下降,我採用了兩個方法進行效能優化,分別是快取起來欄位或屬性的 FieldInfo 或 PropertyInfo 物件,用來減少 GetField 或 GetProperty 方法的呼叫。另一個就是通過 Func<object> 的方法返回靜態屬性或欄位

通過快取 FieldInfo 或 PropertyInfo 物件的方法被我稱為 WithCache 的方法。而通過 Func<object> 的方法返回靜態屬性或欄位的方法被我稱為 GetFieldWithField 或 GetPropertyWithProperty 方法

通過介面 IFieldOrPropertyValueGetter 可以定義不同的方式獲取靜態欄位或屬性,如下面程式碼

        interface IFieldOrPropertyValueGetter
        {
            object GetObject();
        }

        class DelegateValueGetter : IFieldOrPropertyValueGetter
        {
            public DelegateValueGetter(Func<object> getter)
            {
                _getter = getter;
            }

            public object GetObject()
            {
                return _getter();
            }

            private readonly Func<object> _getter;
        }

        class FieldValueGetter : IFieldOrPropertyValueGetter
        {
            public FieldValueGetter(FieldInfo fieldInfo)
            {
                _fieldInfo = fieldInfo;
            }

            public object GetObject()
            {
                return _fieldInfo.GetValue(null);
            }

            private readonly FieldInfo _fieldInfo;
        }

        class PropertyValueGetter : IFieldOrPropertyValueGetter
        {
            public PropertyValueGetter(PropertyInfo propertyInfo)
            {
                _propertyInfo = propertyInfo;
            }

            public object GetObject()
            {
                return _propertyInfo.GetValue(null, null);
            }

            private readonly PropertyInfo _propertyInfo;
        }

而根據 Type 和對應的欄位或屬性名可以獲取靜態的欄位或屬性的方法,就需要引數中包含了兩個引數,一個是 Type 一個 Name 代表欄位或屬性名。構建出的字典如下

Dictionary<(Type type, string name), IFieldOrPropertyValueGetter>

實現的通過快取獲取靜態的欄位或屬性方法如下

        private bool GetFieldOrPropertyValueWithCache(Type type, string name, out object value,
            Dictionary<(Type type, string name), IFieldOrPropertyValueGetter> creatorDictionary)
        {
            if (!creatorDictionary.TryGetValue((type, name), out var creator))
            {
                creator = GetCreator(type, name);
                creatorDictionary.Add((type, name), creator);
            }

            if (creator != null)
            {
                value = creator.GetObject();
                return true;
            }
            else
            {
                value = null;
                return false;
            }
        }

在沒有從快取字典裡面獲取到的時候,將會呼叫 GetCreator 方法獲取建立器。當然了上面的命名是有鍋的,應該是獲取器才對,而不是 creator 建立器

效能測試的程式碼如下

    public static class Foo
    {
        public static string Property { get; } = "Property";

        public static string Field = "Field";
    }

    public class Program
    {
        static void Main(string[] args)
        {
            var program = new Program();
            program.GetFieldWithField(new object[10]);

            BenchmarkRunner.Run<Program>();
        }


        [Benchmark()]
        [ArgumentsSource(nameof(GetTime))]
        public object GetFieldWithCache(object[] getObjectTimeList)
        {
            var creatorDictionary = new Dictionary<(Type type, string name), IFieldOrPropertyValueGetter>();
            for (var i = 0; i < getObjectTimeList.Length; i++)
            {
                GetFieldOrPropertyValueWithCache(typeof(Foo), "Field", out var value, creatorDictionary);

                getObjectTimeList[i] = value;
            }

            return getObjectTimeList;
        }

        [Benchmark()]
        [ArgumentsSource(nameof(GetTime))]
        public object GetPropertyWithCache(object[] getObjectTimeList)
        {
            var creatorDictionary = new Dictionary<(Type type, string name), IFieldOrPropertyValueGetter>();
            for (var i = 0; i < getObjectTimeList.Length; i++)
            {
                GetFieldOrPropertyValueWithCache(typeof(Foo), "Property", out var value, creatorDictionary);

                getObjectTimeList[i] = value;
            }

            return getObjectTimeList;
        }


        [Benchmark()]
        [ArgumentsSource(nameof(GetTime))]
        public object GetPropertyWithProperty(object[] getObjectTimeList)
        {
            var creatorDictionary = new Dictionary<(Type type, string name), IFieldOrPropertyValueGetter>()
            {
                {(typeof(Foo), "Property"), new DelegateValueGetter(() => Foo.Property)}
            };
            for (var i = 0; i < getObjectTimeList.Length; i++)
            {
                GetFieldOrPropertyValueWithCache(typeof(Foo), "Property", out var value, creatorDictionary);

                getObjectTimeList[i] = value;
            }

            return getObjectTimeList;
        }

        [Benchmark()]
        [ArgumentsSource(nameof(GetTime))]
        public object GetFieldWithField(object[] getObjectTimeList)
        {
            var creatorDictionary = new Dictionary<(Type type, string name), IFieldOrPropertyValueGetter>()
            {
                {(typeof(Foo), "Field"), new DelegateValueGetter(() => Foo.Field)}
            };
            for (var i = 0; i < getObjectTimeList.Length; i++)
            {
                GetFieldOrPropertyValueWithCache(typeof(Foo), "Field", out var value, creatorDictionary);

                getObjectTimeList[i] = value;
            }

            return getObjectTimeList;
        }

        [Benchmark()]
        [ArgumentsSource(nameof(GetTime))]
        public object GetFieldWithOriginMethod(object[] getObjectTimeList)
        {
            for (var i = 0; i < getObjectTimeList.Length; i++)
            {
                GetFieldOrPropertyValue(typeof(Foo), "Field", out var value);

                getObjectTimeList[i] = value;
            }

            return getObjectTimeList;
        }

        [Benchmark(Baseline = true)]
        [ArgumentsSource(nameof(GetTime))]
        public object GetPropertyWithOriginMethod(object[] getObjectTimeList)
        {
            for (var i = 0; i < getObjectTimeList.Length; i++)
            {
                GetFieldOrPropertyValue(typeof(Foo), "Property", out var value);

                getObjectTimeList[i] = value;
            }

            return getObjectTimeList;
        }

        public IEnumerable<object[]> GetTime()
        {
            foreach (var count in GetTimeInner())
            {
                yield return new object[] {new object[count]};
            }

            IEnumerable<int> GetTimeInner()
            {
                yield return 1;
                yield return 2;
                yield return 10;
                yield return 100;
                yield return 1000;
            }
        }

        interface IFieldOrPropertyValueGetter
        {
            object GetObject();
        }

        class DelegateValueGetter : IFieldOrPropertyValueGetter
        {
            public DelegateValueGetter(Func<object> getter)
            {
                _getter = getter;
            }

            public object GetObject()
            {
                return _getter();
            }

            private readonly Func<object> _getter;
        }

        class FieldValueGetter : IFieldOrPropertyValueGetter
        {
            public FieldValueGetter(FieldInfo fieldInfo)
            {
                _fieldInfo = fieldInfo;
            }

            public object GetObject()
            {
                return _fieldInfo.GetValue(null);
            }

            private readonly FieldInfo _fieldInfo;
        }

        class PropertyValueGetter : IFieldOrPropertyValueGetter
        {
            public PropertyValueGetter(PropertyInfo propertyInfo)
            {
                _propertyInfo = propertyInfo;
            }

            public object GetObject()
            {
                return _propertyInfo.GetValue(null, null);
            }

            private readonly PropertyInfo _propertyInfo;
        }

        private bool GetFieldOrPropertyValueWithCache(Type type, string name, out object value,
            Dictionary<(Type type, string name), IFieldOrPropertyValueGetter> creatorDictionary)
        {
            if (!creatorDictionary.TryGetValue((type, name), out var creator))
            {
                creator = GetCreator(type, name);
                creatorDictionary.Add((type, name), creator);
            }

            if (creator != null)
            {
                value = creator.GetObject();
                return true;
            }
            else
            {
                value = null;
                return false;
            }
        }

        private IFieldOrPropertyValueGetter GetCreator(Type type, string name)
        {
            FieldInfo field = null;
            Type temp = type;

            do
            {
                field = temp.GetField(name, BindingFlags.Public | BindingFlags.Static);
                if (field != null)
                {
                    return new FieldValueGetter(field);
                }

                temp = temp.BaseType;
            } while (temp != null);


            PropertyInfo prop = null;
            temp = type;

            do
            {
                prop = temp.GetProperty(name, BindingFlags.Public | BindingFlags.Static);
                if (prop != null)
                {
                    return new PropertyValueGetter(prop);
                }

                temp = temp.BaseType;
            } while (temp != null);

            return null;
        }

        private bool GetFieldOrPropertyValue(Type type, string name, out object value)
        {
            FieldInfo field = null;
            Type temp = type;

            do
            {
                field = temp.GetField(name, BindingFlags.Public | BindingFlags.Static);
                if (field != null)
                {
                    value = field.GetValue(null);
                    return true;
                }

                temp = temp.BaseType;
            } while (temp != null);


            PropertyInfo prop = null;
            temp = type;

            do
            {
                prop = temp.GetProperty(name, BindingFlags.Public | BindingFlags.Static);
                if (prop != null)
                {
                    value = prop.GetValue(null, null);
                    return true;
                }

                temp = temp.BaseType;
            } while (temp != null);

            value = null;
            return false;
        }
    }

效能測試如下,大家也可以自己跑一下

Method執行次數MeanErrorStdDevMedianRatio
GetFieldWithCache1000184.28 ns3.760 ns8.937 ns181.24 ns0.87
GetPropertyWithCache1000333.72 ns3.558 ns3.154 ns333.82 ns1.52
GetPropertyWithProperty1000157.95 ns2.842 ns2.519 ns157.88 ns0.72
GetFieldWithField1000151.52 ns2.469 ns2.189 ns151.14 ns0.69
GetFieldWithOriginMethod100074.31 ns0.988 ns0.876 ns74.07 ns0.34
GetPropertyWithOriginMethod1000219.91 ns4.371 ns6.128 ns217.90 ns1.00
GetFieldWithCache100199.02 ns5.517 ns16.007 ns199.47 ns0.94
GetPropertyWithCache100385.85 ns8.923 ns26.030 ns389.29 ns1.77
GetPropertyWithProperty100156.59 ns2.109 ns1.973 ns156.23 ns0.71
GetFieldWithField100153.75 ns3.155 ns3.240 ns152.58 ns0.70
GetFieldWithOriginMethod10077.35 ns1.539 ns2.107 ns77.70 ns0.35
GetPropertyWithOriginMethod100228.61 ns6.463 ns18.544 ns219.22 ns1.06
GetFieldWithCache10199.89 ns5.461 ns16.102 ns201.19 ns0.94
GetPropertyWithCache10344.20 ns6.926 ns15.633 ns339.23 ns1.62
GetPropertyWithProperty10155.89 ns2.431 ns2.274 ns155.34 ns0.71
GetFieldWithField10148.79 ns1.975 ns1.847 ns148.61 ns0.67
GetFieldWithOriginMethod1073.58 ns0.759 ns0.710 ns73.63 ns0.33
GetPropertyWithOriginMethod10216.26 ns1.804 ns1.507 ns216.62 ns0.98
GetFieldWithCache1175.15 ns1.928 ns1.610 ns175.07 ns0.80
GetPropertyWithCache1335.90 ns3.287 ns3.074 ns335.84 ns1.52
GetPropertyWithProperty1166.51 ns4.381 ns12.570 ns161.50 ns0.81
GetFieldWithField1150.82 ns2.063 ns1.723 ns151.13 ns0.69
GetFieldWithOriginMethod173.73 ns0.593 ns0.555 ns73.68 ns0.33
GetPropertyWithOriginMethod1216.67 ns1.991 ns1.765 ns216.38 ns0.98
GetFieldWithCache2175.28 ns3.448 ns4.105 ns174.05 ns0.79
GetPropertyWithCache2357.45 ns7.190 ns16.521 ns352.56 ns1.63
GetPropertyWithProperty2167.96 ns3.619 ns10.385 ns166.05 ns0.79
GetFieldWithField2166.61 ns5.091 ns14.851 ns163.09 ns0.74
GetFieldWithOriginMethod278.34 ns1.626 ns4.559 ns77.55 ns0.38
GetPropertyWithOriginMethod2230.22 ns4.547 ns11.153 ns228.05 ns1.06

上面測試中的 GetFieldWithCache 和 GetPropertyWithCache 分別表示通過快取的方法,減少呼叫 GetField 或 GetProperty 方法去獲取欄位或屬性的 FieldInfo 或 PropertyInfo 物件,但依然使用 GetValue 的方法反射讀取屬性

而 GetPropertyWithProperty 和 GetFieldWithField 方法則是建立委託的方式,返回的就是具體的靜態欄位或屬性

上面程式碼中效能最好的 GetFieldWithOriginMethod 其實就是 WPF 中原本讀取靜態欄位的方法,裡面完全用到反射,沒有加上快取。而 GetPropertyWithOriginMethod 就是對應的 WPF 中原本讀取靜態屬性的方法,可以看到反射讀取靜態速度的效能其實還是很好的

為什麼效能測試的結果是這樣的,原因是建立快取以及建立快取的 Key 的時間比預期的長很多,因此導致了其實不加快取的效能更好

上面測試能否說明反射獲取靜態屬性的效能比不過反射獲取靜態欄位的值。因此根據上面的測試,可以看到反射獲取靜態屬性 GetPropertyWithOriginMethod 的時間是 230.22 ns 左右。而反射獲取靜態欄位的時間是 78.34 ns 左右。其實不能,原因是在 WPF 原始碼裡面是先嚐試讀取靜態欄位,在讀取不到的時候,才去讀取靜態屬性,因此靜態屬性讀取速度會比靜態欄位慢

因為沒有發現當前我的加上快取的優化能比原先的方法效能更好,因此我就不敢將程式碼提到 WPF 官方

程式碼放在 github 歡迎小夥伴訪問

知識共享許可協議
本作品採用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可。歡迎轉載、使用、重新發布,但務必保留文章署名林德熙(包含連結:http://blog.csdn.net/lindexi_gd ),不得用於商業目的,基於本文修改後的作品務必以相同的許可釋出。如有任何疑問,請與我聯絡。