写点什么

寻找性能更优秀的不可变小字典

用户头像
newbe36524
关注
发布于: 2020 年 11 月 08 日

Dictionary 是一个很常用的键值对管理数据结构。但是在性能要求严苛的情况下,字典的查找速度并不高。所以,我们需要更快的方案。



需求说明



这里,我们需要一个 PropertyInfo 和委托对应的映射关系,这样我们就可以存储《寻找性能更优秀的动态 Getter 和 Setter 方案》提到的委托。



因此,这个字典有这些特点:



  1. 这个字典一旦创建就不需要修改。

  2. 字典项目并不多,因为通常一个 class 不会有太多属性。



方案说明



方案 1,Switch 表达式法。使用表达式生成一个包含 switch case 语句的委托。



方案 2,数组跳表。我们知道,switch case 之所以比连续的 if else 要快的原因是因为其生成的 IL 中包含一个跳表算法。因此,如果我们有办法使用连续数字作为下标,以及一个数组。就可以在 C# 中自己实现跳表。



知识要点



  1. 使用表达式创建委托

  2. PropertyInfo 有一个 int MetadataToken 属性,根据目前的观察,可以知道在一个类型中的属性其 MetadataToken 似乎是连续的,因此可以取模后作为跳表的 key。

  3. 所谓的跳表,可以简单理解为,使用数组的下标来定位数组中的特定元素。



实现代码



这里,我们直接给出基准测试中使用的代码。



其中:



  • 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);
}
}

基准测试



BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.572 (2004/?/20H1)
Intel Xeon CPU E5-2678 v3 2.50GHz, 1 CPU, 24 logical and 12 physical cores
.NET Core SDK=5.0.100-rc.2.20479.15
[Host] : .NET Core 2.1.23 (CoreCLR 4.6.29321.03, CoreFX 4.6.29321.01), X64 RyuJIT
net461 : .NET Framework 4.8 (4.8.4250.0), X64 RyuJIT
net48 : .NET Framework 4.8 (4.8.4250.0), X64 RyuJIT
netcoreapp21 : .NET Core 2.1.23 (CoreCLR 4.6.29321.03, CoreFX 4.6.29321.01), X64 RyuJIT
netcoreapp31 : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT
netcoreapp5 : .NET Core 5.0.0 (CoreCLR 5.0.20.47505, CoreFX 5.0.20.47505), X64 RyuJIT

结论



  1. 字典真拉胯。

  2. Framework 真拉胯。

  3. Net 5 简直太强了。

  4. 数组跳表是非直接方案中最快的。



图表





总结



不论是数组跳表还是表达式 Switch 方案都可以解决这个问题,而且都要比使用字典要快。



但是这里有一个问题,就是目前作者还没有找到任何有关 MetadataToken 是否真的具备同 class 连续的性质。



因此建议还是使用 Switch 方案实现。



我只是知识的搬运工





发布说明





使用样例





番外分享





GitHub 项目地址:https://github.com/newbe36524/Newbe.ObjectVisitor



Gitee 项目地址:https://gitee.com/yks/Newbe.ObjectVisitor





发布于: 2020 年 11 月 08 日阅读数: 667
用户头像

newbe36524

关注

开源项目作者,独立博客维护者 2020.06.02 加入

newbe36524 是一名开发工程师。newbe 是 newbe36524 的个人技术博客。秉承 now everyone will be excellent 的 slogan 希望为开发者们分享自己的开发经验和心路历程。

评论

发布
暂无评论
寻找性能更优秀的不可变小字典