探索一下 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
Method Mean Error StdDev Median Ratio RatioSD Gen0 Allocated Alloc Ratio
SVEnumsToEnumInt 0.9796 ns 0.0062 ns 0.0055 ns 0.9781 ns 0.02 0.00 - - 0.00
SVEnumsToEnumIntByte 1.0990 ns 0.0089 ns 0.0074 ns 1.0966 ns 0.03 0.00 - - 0.00
SVEnumsToEnumIntObject 5.1211 ns 0.0842 ns 0.0746 ns 5.1295 ns 0.12 0.00 0.0029 24 B 1.00
ToEnumIntByte 40.9720 ns 0.2100 ns 0.1861 ns 40.9065 ns 1.00 0.03 0.0029 24 B 1.00
ToEnumInt 41.1962 ns 0.8452 ns 1.4122 ns 40.4985 ns 1.00 0.05 0.0029 24 B 1.00
ToEnumIntObject 48.2590 ns 0.4380 ns 0.3882 ns 48.0802 ns 1.17 0.04 0.0057 48 B 2.00
Method Categories Mean Error StdDev Ratio RatioSD Gen0 Gen1 Allocated Alloc Ratio
SVEnumsParse 2.8382 ns 0.0508 ns 0.0450 ns 0.12 0.00 - - - NA
FastEnumParse 6.9671 ns 0.0437 ns 0.0388 ns 0.28 0.00 - - - NA
EnumInfoParse 7.1513 ns 0.1049 ns 0.0930 ns 0.29 0.00 - - - NA
Parse 24.5338 ns 0.0548 ns 0.0485 ns 1.00 0.00 - - - NA
FastEnumGetName GetName 0.8608 ns 0.0175 ns 0.0155 ns 0.32 0.01 - - - NA
EnumInfoGetName GetName 1.4291 ns 0.0147 ns 0.0130 ns 0.54 0.01 - - - NA
SVEnumsGetName GetName 1.6210 ns 0.0148 ns 0.0131 ns 0.61 0.01 - - - NA
GetName GetName 2.6512 ns 0.0150 ns 0.0125 ns 1.00 0.01 - - - NA
SVEnumsGetNames GetNames 0.2539 ns 0.0061 ns 0.0051 ns 0.01 0.00 - - - 0.00
FastEnumGetNames GetNames 0.6874 ns 0.0195 ns 0.0163 ns 0.03 0.00 - - - 0.00
GetNames GetNames 21.0463 ns 0.4645 ns 0.5162 ns 1.00 0.03 0.0239 0.0001 200 B 1.00
SVEnumsGetValues GetValues 0.3022 ns 0.0296 ns 0.0277 ns 0.009 0.00 - - - 0.00
FastEnumGetValues GetValues 0.6683 ns 0.0098 ns 0.0082 ns 0.021 0.00 - - - 0.00
GetValues GetValues 32.5145 ns 0.6732 ns 0.5968 ns 1.000 0.03 0.0134 - 112 B 1.00
SVEnumsParseIgnoreCase IgnoreCase 3.0465 ns 0.0680 ns 0.0727 ns 0.12 0.00 - - - NA
EnumInfoParseIgnoreCase IgnoreCase 10.1299 ns 0.1660 ns 0.1472 ns 0.42 0.01 - - - NA
FastEnumParseIgnoreCase IgnoreCase 10.3531 ns 0.0807 ns 0.0674 ns 0.42 0.00 - - - NA
ParseIgnoreCase IgnoreCase 24.3767 ns 0.1270 ns 0.1060 ns 1.00 0.01 - - - NA
SVEnumsIsDefinedName IsDefinedName 2.7188 ns 0.0111 ns 0.0098 ns 0.11 0.00 - - - NA
EnumInfoIsDefinedName IsDefinedName 6.6075 ns 0.0190 ns 0.0148 ns 0.26 0.00 - - - NA
FastEnumIsDefinedName IsDefinedName 6.7011 ns 0.0388 ns 0.0303 ns 0.26 0.00 - - - NA
IsDefinedName IsDefinedName 25.3131 ns 0.2064 ns 0.1829 ns 1.00 0.01 - - - NA

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

探索一下 Enum 优化的更多相关文章

  1. [转]探索 Android 内存优化方法

    前言 这篇文章的内容是我回顾和再学习 Android 内存优化的过程中整理出来的,整理的目的是让我自己对 Android 内存优化相关知识的认识更全面一些,分享的目的是希望大家也能从这些知识中得到一些 ...

  2. 美图App的移动端DNS优化实践:HTTPS请求耗时减小近半

    本文引用了颜向群发表于高可用架构公众号上的文章<聊聊HTTPS环境DNS优化:美图App请求耗时节约近半案例>的部分内容,感谢原作者. 1.引言 移动互联网时代,APP 厂商之间的竞争非常 ...

  3. 备战双十一,腾讯WeTest有高招——小程序质量优化必读

    WeTest 导读 2018年双十一战场小程序购物通道表现不俗,已逐渐成为各大品牌方角逐的新战场.数据显示,截止目前95%的电商平台都已经上线了小程序.除了电商企业外,许多传统线下商家也开始重视小程序 ...

  4. 82.使用vue后怎么针对搜索引擎做SEO优化?

    什么是SEO 搜索引擎优化(Search engine optimization,简称SEO),指为了提升网页在搜索引擎自然搜索结果中(非商业性推广结果)的收录数量以及排序位置而做的优化行为,是为了从 ...

  5. 优化故事: BLOOM 模型推理

    经过"九九八十一难",大模型终于炼成.下一步就是架设服务,准备开门营业了.真这么简单?恐怕未必!行百里者半九十,推理优化又是新的雄关漫道.如何进行延迟优化?如何进行成本优化 (别忘 ...

  6. iOS开发-LayoutGuide(从top/bottom LayoutGuide到Safe Area)

    iOS7 topLayoutGuide/bottomLayoutGuide 创建一个叫做LayoutGuideStudy的工程,我们打开看一下Main.storyboard: storyboard-t ...

  7. SVG的动态之美-搜狗地铁图重构散记

    搜狗地图发布了新版的移动端地铁图,改版初衷是为了用户交互体验的提升以及性能的改善.原版地铁图被用户吐槽最多的是pinch缩放不流畅.无过渡动画.拖拽边界不合理等等,大体上都是交互体验上的问题.实际上原 ...

  8. 品味性能之道<十一>:JAVA中switch和if性能比较

    通常而言大家普遍的认知里switch case的效率高于if else.根据我的理解而言switch的查找类似于二叉树,if则是线性查找.按照此逻辑推理对于对比条件数目大于3时switch更优,并且对 ...

  9. 《Bandwidth-Aware Scheduling With SDN in Hadoop:A New Trend for Big Data》--2017

    Hadoop中使用SDN的带宽感知调度:大数据的一种新趋势 Abstract: 为了处理大规模的数据,提出了基于Hadoop框架的MapReduce,在Hadoop系统中,有一种叫做NP完全最小(NP ...

  10. 《Noisy Activation Function》噪声激活函数(一)

    本系列文章由 @yhl_leo 出品,转载请注明出处. 文章链接: http://blog.csdn.net/yhl_leo/article/details/51736830 Noisy Activa ...

随机推荐

  1. 学习.NET 8 MiniApis入门

    介绍篇 什么是MiniApis? MiniApis的特点和优势 MiniApis的应用场景 环境搭建 系统要求 安装MiniApis 配置开发环境 基础概念 MiniApis架构概述 关键术语解释(如 ...

  2. P9358 题解

    不难发现,最开始有 \(n\) 条链,并且由于每个点最多有一个桥,所以我们的交换操作实际上等价于将相邻的两条链断开,然后将它们后半部分交换.并且每个点在路径中的相对位置不变. 于是考虑维护这些链. 有 ...

  3. Dubbo依赖

    项目依赖 Dubbo依赖 <!--Dubbo依赖--> <dependency> <groupId>com.alibaba</groupId> < ...

  4. 解决方案 | vbnet的msgbox 窗口最前置,topmost属性设置

    For that you can use the TopMost Property of MsgBox (Number 262144) MsgBox("Hello there", ...

  5. 2024秋招西山居游戏开发SEED种子实习笔试题

    西山居游戏开发SEED种子实习 2024年秋招笔试题目,仅供参考,请大佬多多指教 选择题 逆波兰数,TCP,操作系统FIFO,C语言大小端 填空题 一道LUA脚本写结果,一道并发存储优化题,计算机系统 ...

  6. WebGL压缩纹理实践

    0x01 本文将讲述压缩纹理在实际项目中的使用的案例.最近的一个项目是这样的:项目由于涉及到的建筑物特别多,大概有近40栋的建筑,而每一栋建筑物,又有10层楼,每层楼里面又有很多的设备.这就导致我们需 ...

  7. Django日期字段默认值default=timezone.now

    如果你确实希望默认值是当前日期和时间,Django 提供了一个方便的函数 django.utils.timezone.now 来实现这一目的. 你可以这样调整你的模型,以使用当前日期和时间作为默认值: ...

  8. Rust 中 *、&、mut、&mut、ref、ref mut 的用法和区别

    Rust 中 *.&.mut.&mut.ref.ref mut 的用法和区别 在 Rust 中,*.ref.mut.& 和 ref mut 是用于处理引用.解引用和可变性的关键 ...

  9. java面试一日一题:mysql执行delete数据真的被删除了吗

    问题:请讲下mysql执行了delete操作,数据真的被删除了吗 分析:这个问题考察对mysql底层存储的理解. 回答要点: 主要从以下几点去考虑, 1.肯定没有真正删除? 2.为什么这样设计? my ...

  10. 2、SpringBoot2之入门案例

    2.1.创建Maven工程 2.1.1.创建空项目 2.1.2.设置项目名称和路径 2.1.3.设置项目sdk 2.1.4.项目初始状态 注意:需要关闭项目再重新打开,才能看到SpringBoot-P ...