探索一下 Enum 优化
 作者:八苦-瞿昙
- 2024-10-18  中国台湾
- 本文字数:5856 字 - 阅读完需:约 19 分钟 
探索一下 Enum 优化
SV.Enums主要是探索如何让 enum 更高效
其中涉及的优化手段并非完全自创
很多内容参考于以下项目
主要优化手段
其实主要全是 空间换时间,大量缓存
封装入口方法以及 source-generators 生成
不过本项目尝试了封装入口方法、ModuleInitializer、source-generators 来避免对使用影响(其实更主要是尝试如何避免使用interceptors)
    public static class Enums<T> where T : struct, Enum    {        public static bool IsFlags => CheckInfo().IsFlags;        public static bool IsEmpty => CheckInfo().IsEmpty;
        internal static IEnumInfo<T> Info;
        [MethodImpl(Enums.Optimization)]        internal static IEnumInfo<T> CheckInfo()        {            if (Info == null)            {                Info = new EnumInfo<T>();            }            return Info;        }
        public static T Parse(string name, bool ignoreCase)        {            if (CheckInfo().TryParse(name, ignoreCase, out var result))                return result;            throw new ArgumentException($"Specified value '{name}' is not defined.", nameof(name));        }
复制代码
 这样做主要就可以利用 source-generators 生成 enum 处理代码,
并通过 ModuleInitializer 在运行时启用生成的 enum 代码
如下为生成 enum 代码示例
internal class EnumInfoAD125120120540FC9AA056E2DD394A7C : EnumBase<global::Benchmark.Fruits2>    {        public override bool IsDefined(string name)        {            return name switch            {                nameof(global::Benchmark.Fruits2.Apple) => true,nameof(global::Benchmark.Fruits2.Lemon) => true,nameof(global::Benchmark.Fruits2.Melon) => true,nameof(global::Benchmark.Fruits2.Banana) => true,                _ => false,            };        }
        public override string? GetName(global::Benchmark.Fruits2 t)        {            switch (t)            {                case global::Benchmark.Fruits2.Apple: return nameof(global::Benchmark.Fruits2.Apple);case global::Benchmark.Fruits2.Lemon: return nameof(global::Benchmark.Fruits2.Lemon);case global::Benchmark.Fruits2.Melon: return nameof(global::Benchmark.Fruits2.Melon);case global::Benchmark.Fruits2.Banana: return nameof(global::Benchmark.Fruits2.Banana);                default:                    return null;            }        }
        protected override bool TryParseCase(in ReadOnlySpan<char> name, out global::Benchmark.Fruits2 result)        {            switch (name)            {                case ReadOnlySpan<char> current when current.Equals(nameof(global::Benchmark.Fruits2.Apple).AsSpan(), global::System.StringComparison.Ordinal):                    result = global::Benchmark.Fruits2.Apple;                    return true;                case ReadOnlySpan<char> current when current.Equals(nameof(global::Benchmark.Fruits2.Lemon).AsSpan(), global::System.StringComparison.Ordinal):                    result = global::Benchmark.Fruits2.Lemon;                    return true;                case ReadOnlySpan<char> current when current.Equals(nameof(global::Benchmark.Fruits2.Melon).AsSpan(), global::System.StringComparison.Ordinal):                    result = global::Benchmark.Fruits2.Melon;                    return true;                case ReadOnlySpan<char> current when current.Equals(nameof(global::Benchmark.Fruits2.Banana).AsSpan(), global::System.StringComparison.Ordinal):                    result = global::Benchmark.Fruits2.Banana;                    return true;                default:                    result = default;                    return false;            }        }
        protected override bool TryParseIgnoreCase(in ReadOnlySpan<char> name, out global::Benchmark.Fruits2 result)        {            switch (name)            {                case ReadOnlySpan<char> current when current.Equals(nameof(global::Benchmark.Fruits2.Apple).AsSpan(), global::System.StringComparison.OrdinalIgnoreCase):                    result = global::Benchmark.Fruits2.Apple;                    return true;                case ReadOnlySpan<char> current when current.Equals(nameof(global::Benchmark.Fruits2.Lemon).AsSpan(), global::System.StringComparison.OrdinalIgnoreCase):                    result = global::Benchmark.Fruits2.Lemon;                    return true;                case ReadOnlySpan<char> current when current.Equals(nameof(global::Benchmark.Fruits2.Melon).AsSpan(), global::System.StringComparison.OrdinalIgnoreCase):                    result = global::Benchmark.Fruits2.Melon;                    return true;                case ReadOnlySpan<char> current when current.Equals(nameof(global::Benchmark.Fruits2.Banana).AsSpan(), global::System.StringComparison.OrdinalIgnoreCase):                    result = global::Benchmark.Fruits2.Banana;                    return true;                default:                    result = default;                    return false;            }        }    }
        internal static partial class EnumsF1029F0E5915401BBDD8559E2B5289B1    {        [ModuleInitializer]        internal static void Init4771B8A4BD2E4761973279D81E61089C()        {            global::SV.Enums.SetEnumInfo<global::Benchmark.Fruits2>(new EnumInfoAD125120120540FC9AA056E2DD394A7C());        }    }
复制代码
 不过这样封装入口,存在一定性能损失
空间换时间
当然如果不使用 source-generators, 对应功能也有默认实现
部分代码如下, 大部分东西都内存缓存了
    public class EnumInfo<T> : IEnumInfo<T> where T : struct, Enum    {        private readonly string[] names;        private readonly T[] values;        private readonly (string Name, T Value)[] members;        private readonly FastReadOnlyDictionary<string, T> membersByName;        private readonly FastReadOnlyDictionary<T, (string Name, EnumMemberAttribute Member, FastReadOnlyDictionary<int, string> Labels)> namesByMember;        private readonly Type underlyingType;        private readonly TypeCode underlyingTypeCode;
        public bool IsFlags { get; private set; }        public bool IsEmpty => values.Length == 0;
        public EnumInfo() : base()        {            var t = typeof(T);            names = Enum.GetNames(t);            members = names.Select(i => (i, (T)Enum.Parse(t, i))).ToArray();            values = members.Select(i => i.Value).ToArray();            membersByName = members.ToFastReadOnlyDictionary(i => i.Name, i => i.Value);            namesByMember = membersByName.AsEnumerable().DistinctBy(i => i.Value).ToFastReadOnlyDictionary(i => i.Value, i =>            {                var fieldInfo = t.GetField(i.Key)!;                return (i.Key, fieldInfo.GetCustomAttribute<EnumMemberAttribute>(), fieldInfo.GetCustomAttributes<LabelAttribute>().DistinctBy(i => i.Index).ToFastReadOnlyDictionary(x => x.Index, x => x.Value));            });            underlyingType = Enum.GetUnderlyingType(t);            underlyingTypeCode = Type.GetTypeCode(underlyingType);            IsFlags = t.IsDefined(typeof(FlagsAttribute), true);        }
        [MethodImpl(Enums.Optimization)]        public bool TryParseIgnoreCase(in ReadOnlySpan<char> name, out T result)        {            foreach (var member in members.AsSpan())            {                if (name.Equals(member.Name.AsSpan(), StringComparison.OrdinalIgnoreCase))                {                    result = member.Value;                    return true;                }            }            result = default;            return false;        }
复制代码
 enum 转换方法
同时提供一些 不用拆箱装箱的 enum 转换方法,里面移除了类型检查的逻辑,所以理论只能保证正常使用不会有问题
public static T ToEnum(int value)
public static T ToEnum(byte value)
public static T ToEnum(Int16 value)
public static T ToEnum(Int64 value)
...
复制代码
 性能测试
简单做下性能测试, 部分代码如下
public enum Fruits    {        Apple,        Lemon,        Melon,        Banana,        Lemon1,        Melon2,        Banana3,        Lemon11,        Melon21,        Banana31,        Lemon12,        Melon22,        Banana32,        Lemon13,        Melon23,        Banana33,        Lemon131,        Melon231,        Banana331,        Lemon14,        Melon24,        Banana34,    }
    [MemoryDiagnoser, Orderer(summaryOrderPolicy: SummaryOrderPolicy.FastestToSlowest), GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory), CategoriesColumn]    public class EnumBenchmarks    {        private readonly EnumInfo<Fruits> test;
        public EnumBenchmarks()        {            test = new EnumInfo<Fruits>();        }
        [Benchmark(Baseline = true), BenchmarkCategory("IgnoreCase")]        public Fruits ParseIgnoreCase()        {            return Enum.Parse<Fruits>("melon", true);        }
        [Benchmark, BenchmarkCategory("IgnoreCase")]        public Fruits FastEnumParseIgnoreCase()        {            return FastEnum.Parse<Fruits>("melon", true);        }
        [Benchmark, BenchmarkCategory("IgnoreCase")]        public Fruits SVEnumsParseIgnoreCase()        {            Enums<Fruits>.TryParse("melon", true, out var v);            return v;        }
        [Benchmark, BenchmarkCategory("IgnoreCase")]        public Fruits EnumInfoParseIgnoreCase()        {            test.TryParse("melon", true, out var v);            return v;        }
        [Benchmark(Baseline = true)]        public Fruits Parse()        {            return Enum.Parse<Fruits>("Melon", false);        }
        [Benchmark]        public Fruits FastEnumParse()        {            return FastEnum.Parse<Fruits>("Melon", false);        }
        [Benchmark]        public Fruits SVEnumsParse()        {            Enums<Fruits>.TryParse("Melon", out var v);            return v;        }
        [Benchmark]        public Fruits EnumInfoParse()        {            test.TryParse("Melon", false, out var v);            return v;        }
        ...
    [MemoryDiagnoser, Orderer(summaryOrderPolicy: SummaryOrderPolicy.FastestToSlowest), GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory), CategoriesColumn]    public class ToEnumBenchmarks    {        private readonly EnumInfo<Fruits> test;
        public ToEnumBenchmarks()        {            test = new EnumInfo<Fruits>();            //Enums.SetEnumInfo<Fruits>(new TestIEnumInfo());        }
        [Benchmark(Baseline = true), BenchmarkCategory("ToEnumInt")]        public Fruits ToEnumInt()        {            return (Fruits)Enum.ToObject(typeof(Fruits), 11);        }
        [Benchmark, BenchmarkCategory("ToEnumInt")]        public Fruits SVEnumsToEnumInt()        {            return Enums<Fruits>.ToEnum(11);        }
        [Benchmark, BenchmarkCategory("ToEnumInt")]        public Fruits ToEnumIntByte()        {            return (Fruits)Enum.ToObject(typeof(Fruits), (byte)11);        }
        [Benchmark, BenchmarkCategory("ToEnumInt")]        public Fruits SVEnumsToEnumIntByte()        {            return Enums<Fruits>.ToEnum((byte)11);        }
        [Benchmark, BenchmarkCategory("ToEnumInt")]        public Fruits ToEnumIntObject()        {            return (Fruits)Enum.ToObject(typeof(Fruits), (object)11);        }
        [Benchmark, BenchmarkCategory("ToEnumInt")]        public Fruits SVEnumsToEnumIntObject()        {            return Enums<Fruits>.ToEnum((object)11);        }    }
复制代码
 结果如下
BenchmarkDotNet v0.14.0, Windows 10 (10.0.19045.4651/22H2/2022Update)Intel Core i7-10700 CPU 2.90GHz, 1 CPU, 16 logical and 8 physical cores.NET SDK 9.0.100-preview.5.24307.3  [Host]     : .NET 8.0.6 (8.0.624.26715), X64 RyuJIT AVX2  DefaultJob : .NET 8.0.6 (8.0.624.26715), X64 RyuJIT AVX2
复制代码
 划线
评论
复制
发布于: 刚刚阅读数: 3
版权声明: 本文为 InfoQ 作者【八苦-瞿昙】的原创文章。
原文链接:【http://xie.infoq.cn/article/d865362313beb0e52cd487bc2】。文章转载请联系作者。

八苦-瞿昙
关注
一个假和尚,不懂人情世故。 2018-11-23 加入
会点点技术,能写些代码,只爱静静。 g hub: https://github.com/fs7744 黑历史:https://www.cnblogs.com/fs7744







 
    
 
				 
				 
			


评论