Dictionary 是一个很常用的键值对管理数据结构。但是在性能要求严苛的情况下,字典的查找速度并不高。所以,我们需要更快的方案。
需求说明这里,我们需要一个 PropertyInfo 和委托对应的映射关系,这样我们就可以存储《寻找性能更优秀的动态 Getter 和 Setter 方案》提到的委托。
因此,这个字典有这些特点:
这个字典一旦创建就不需要修改。
字典项目并不多,因为通常一个 class 不会有太多属性。
方案说明方案 1,Switch 表达式法。使用表达式生成一个包含 switch case 语句的委托。
方案 2,数组跳表。我们知道,switch case 之所以比连续的 if else 要快的原因是因为其生成的 IL 中包含一个跳表算法。因此,如果我们有办法使用连续数字作为下标,以及一个数组。就可以在 C# 中自己实现跳表。
知识要点使用表达式创建委托
PropertyInfo 有一个 int MetadataToken 属性,根据目前的观察,可以知道在一个类型中的属性其 MetadataToken 似乎是连续的,因此可以取模后作为跳表的 key。
所谓的跳表,可以简单理解为,使用数组的下标来定位数组中的特定元素。
实现代码这里,我们直接给出基准测试中使用的代码。
其中:
Directly 直接读,没有任何查找
ArrayIndex 数组跳表
SwitchExp 表达式生成 Switch 方案
Dic 传统字典方案
using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; using BenchmarkDotNet.Attributes; namespace Newbe.ObjectVisitor.BenchmarkTest { [Config(typeof(Config))] public class FuncSearchTest { private Func<Yueluo, object>[] _target; private readonly Yueluo _yueluo; private readonly Func<Yueluo, string> _func; private readonly PropertyInfo _nameP; private readonly Func<PropertyInfo, Func<Yueluo, object>> _switcher; private readonly Dictionary<PropertyInfo, Func<Yueluo, object>> _dic; public FuncSearchTest() { _yueluo = Yueluo.Create(); var propertyInfos = typeof(Yueluo).GetProperties().ToArray(); CreateCacheArrayD(propertyInfos); _switcher = ValueGetter.CreateGetter<Yueluo, object>(propertyInfos, info => Expression.SwitchCase(Expression.Constant(CreateFunc(info)), Expression.Constant(info))); _dic = propertyInfos.ToDictionary(x => x, CreateFunc); _nameP = typeof(Yueluo).GetProperty(nameof(Yueluo.Name)); _func = x => x.Name; } private void CreateCacheArrayD(IReadOnlyCollection<PropertyInfo> propertyInfos) { _target = new Func<Yueluo, object>[propertyInfos.Count]; foreach (var info in propertyInfos) { var key = GetKey(info); var index = key % propertyInfos.Count; _target[index] = CreateFunc(info); } } private static Func<Yueluo, object> CreateFunc(PropertyInfo info) { var pExp = Expression.Parameter(typeof(Yueluo), "x"); var bodyExp = Expression.Property(pExp, info); var finalExp = Expression.Lambda<Func<Yueluo, object>>(Expression.Convert(bodyExp, typeof(object)), pExp); return finalExp.Compile(); } private static int GetKey(MemberInfo info) { var token = info.MetadataToken; return token; } [Benchmark(Baseline = true)] public string Directly() => _func(_yueluo); [Benchmark] public string ArrayIndex() => (string) _target[_nameP.MetadataToken % _target.Length](_yueluo); [Benchmark] public string SwitchExp() => (string) _switcher(_nameP)(_yueluo); [Benchmark] public string Dic() => (string) _dic[_nameP](_yueluo); } }