写点什么

探索一下 Enum 优化

作者:八苦-瞿昙
  • 2024-10-18
    中国台湾
  • 本文字数:5856 字

    阅读完需:约 19 分钟

探索一下 Enum 优化

SV.Enums主要是探索如何让 enum 更高效


其中涉及的优化手段并非完全自创


很多内容参考于以下项目


主要优化手段

其实主要全是 空间换时间,大量缓存

封装入口方法以及 source-generators 生成

不过本项目尝试了封装入口方法、ModuleInitializersource-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
复制代码




完整代码参见 https://github.com/fs7744/Enums

发布于: 刚刚阅读数: 3
用户头像

八苦-瞿昙

关注

一个假和尚,不懂人情世故。 2018-11-23 加入

会点点技术,能写些代码,只爱静静。 g hub: https://github.com/fs7744 黑历史:https://www.cnblogs.com/fs7744

评论

发布
暂无评论
探索一下 Enum 优化_dotnet_八苦-瞿昙_InfoQ写作社区