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


    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. 数组跳表是非直接方案中最快的。

    图表

    FuncSearch

    数据

    MethodJobRuntimeMeanErrorStdDevRatioRatioSDRank
    Directly net461 .NET 4.6.1 0.9347 ns 0.0363 ns 0.0321 ns 1.00 0.00 1
    ArrayIndex net461 .NET 4.6.1 15.0904 ns 0.3820 ns 0.3752 ns 16.13 0.64 2
    SwitchExp net461 .NET 4.6.1 17.1585 ns 0.0624 ns 0.0521 ns 18.30 0.56 3
    Dic net461 .NET 4.6.1 34.3348 ns 0.2447 ns 0.2169 ns 36.77 1.18 4
                     
    Directly net48 .NET 4.8 0.6338 ns 0.0233 ns 0.0218 ns 1.00 0.00 1
    ArrayIndex net48 .NET 4.8 15.3098 ns 0.2794 ns 0.2613 ns 24.17 0.69 2
    SwitchExp net48 .NET 4.8 17.8113 ns 0.0740 ns 0.0656 ns 28.20 0.98 3
    Dic net48 .NET 4.8 33.7930 ns 0.4538 ns 0.4245 ns 53.36 1.64 4
                     
    Directly netcoreapp21 .NET Core 2.1 1.2153 ns 0.1168 ns 0.1434 ns 1.00 0.00 1
    ArrayIndex netcoreapp21 .NET Core 2.1 4.6545 ns 0.1044 ns 0.0871 ns 4.01 0.51 2
    SwitchExp netcoreapp21 .NET Core 2.1 8.1995 ns 0.2567 ns 0.2747 ns 6.81 0.90 3
    Dic netcoreapp21 .NET Core 2.1 24.2669 ns 0.5440 ns 0.5586 ns 20.07 2.51 4
                     
    Directly netcoreapp31 .NET Core 3.1 0.7382 ns 0.1148 ns 0.1074 ns 1.00 0.00 1
    ArrayIndex netcoreapp31 .NET Core 3.1 4.3580 ns 0.1299 ns 0.1085 ns 6.10 0.77 2
    SwitchExp netcoreapp31 .NET Core 3.1 7.5985 ns 0.1310 ns 0.1161 ns 10.45 1.41 3
    Dic netcoreapp31 .NET Core 3.1 22.2433 ns 0.2756 ns 0.2443 ns 30.61 4.20 4
                     
    Directly netcoreapp5 .NET Core 5.0 1.3323 ns 0.0527 ns 0.0493 ns 1.00 0.00 1
    ArrayIndex netcoreapp5 .NET Core 5.0 5.0058 ns 0.1361 ns 0.1206 ns 3.77 0.15 2
    SwitchExp netcoreapp5 .NET Core 5.0 9.0576 ns 0.0985 ns 0.0921 ns 6.81 0.26 3
    Dic netcoreapp5 .NET Core 5.0 20.4052 ns 0.2724 ns 0.2275 ns 15.44 0.59 4

    总结

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

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

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

    我只是知识的搬运工

    发布说明

    使用样例

    番外分享

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

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

    Newbe.ObjectVisitor

  • 相关阅读:
    你做的页面有哪些浏览器测试过?这些浏览器的内核分别是什么?
    响应式布局
    Promise(二)
    Promise(一)
    同步和异步的自我理解
    null 与 undefined 区别
    python pandas 数据处理
    【python】list、tuple、dict、set、dataframe、narray、series之间的区别
    时间序列常用模型
    Linux系统学习笔记
  • 原文地址:https://www.cnblogs.com/newbe36524/p/13946269.html
Copyright © 2020-2023  润新知