探索一下 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









评论