dotnet 5 靜態欄位和屬性的反射獲取 沒有想象中那麼傷效能
在最近在做 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 | 執行次數 | Mean | Error | StdDev | Median | Ratio |
---|---|---|---|---|---|---|
GetFieldWithCache | 1000 | 184.28 ns | 3.760 ns | 8.937 ns | 181.24 ns | 0.87 |
GetPropertyWithCache | 1000 | 333.72 ns | 3.558 ns | 3.154 ns | 333.82 ns | 1.52 |
GetPropertyWithProperty | 1000 | 157.95 ns | 2.842 ns | 2.519 ns | 157.88 ns | 0.72 |
GetFieldWithField | 1000 | 151.52 ns | 2.469 ns | 2.189 ns | 151.14 ns | 0.69 |
GetFieldWithOriginMethod | 1000 | 74.31 ns | 0.988 ns | 0.876 ns | 74.07 ns | 0.34 |
GetPropertyWithOriginMethod | 1000 | 219.91 ns | 4.371 ns | 6.128 ns | 217.90 ns | 1.00 |
GetFieldWithCache | 100 | 199.02 ns | 5.517 ns | 16.007 ns | 199.47 ns | 0.94 |
GetPropertyWithCache | 100 | 385.85 ns | 8.923 ns | 26.030 ns | 389.29 ns | 1.77 |
GetPropertyWithProperty | 100 | 156.59 ns | 2.109 ns | 1.973 ns | 156.23 ns | 0.71 |
GetFieldWithField | 100 | 153.75 ns | 3.155 ns | 3.240 ns | 152.58 ns | 0.70 |
GetFieldWithOriginMethod | 100 | 77.35 ns | 1.539 ns | 2.107 ns | 77.70 ns | 0.35 |
GetPropertyWithOriginMethod | 100 | 228.61 ns | 6.463 ns | 18.544 ns | 219.22 ns | 1.06 |
GetFieldWithCache | 10 | 199.89 ns | 5.461 ns | 16.102 ns | 201.19 ns | 0.94 |
GetPropertyWithCache | 10 | 344.20 ns | 6.926 ns | 15.633 ns | 339.23 ns | 1.62 |
GetPropertyWithProperty | 10 | 155.89 ns | 2.431 ns | 2.274 ns | 155.34 ns | 0.71 |
GetFieldWithField | 10 | 148.79 ns | 1.975 ns | 1.847 ns | 148.61 ns | 0.67 |
GetFieldWithOriginMethod | 10 | 73.58 ns | 0.759 ns | 0.710 ns | 73.63 ns | 0.33 |
GetPropertyWithOriginMethod | 10 | 216.26 ns | 1.804 ns | 1.507 ns | 216.62 ns | 0.98 |
GetFieldWithCache | 1 | 175.15 ns | 1.928 ns | 1.610 ns | 175.07 ns | 0.80 |
GetPropertyWithCache | 1 | 335.90 ns | 3.287 ns | 3.074 ns | 335.84 ns | 1.52 |
GetPropertyWithProperty | 1 | 166.51 ns | 4.381 ns | 12.570 ns | 161.50 ns | 0.81 |
GetFieldWithField | 1 | 150.82 ns | 2.063 ns | 1.723 ns | 151.13 ns | 0.69 |
GetFieldWithOriginMethod | 1 | 73.73 ns | 0.593 ns | 0.555 ns | 73.68 ns | 0.33 |
GetPropertyWithOriginMethod | 1 | 216.67 ns | 1.991 ns | 1.765 ns | 216.38 ns | 0.98 |
GetFieldWithCache | 2 | 175.28 ns | 3.448 ns | 4.105 ns | 174.05 ns | 0.79 |
GetPropertyWithCache | 2 | 357.45 ns | 7.190 ns | 16.521 ns | 352.56 ns | 1.63 |
GetPropertyWithProperty | 2 | 167.96 ns | 3.619 ns | 10.385 ns | 166.05 ns | 0.79 |
GetFieldWithField | 2 | 166.61 ns | 5.091 ns | 14.851 ns | 163.09 ns | 0.74 |
GetFieldWithOriginMethod | 2 | 78.34 ns | 1.626 ns | 4.559 ns | 77.55 ns | 0.38 |
GetPropertyWithOriginMethod | 2 | 230.22 ns | 4.547 ns | 11.153 ns | 228.05 ns | 1.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 ),不得用於商業目的,基於本文修改後的作品務必以相同的許可釋出。如有任何疑問,請與我聯絡。