LuminPack 1.0.4

dotnet add package LuminPack --version 1.0.4
                    
NuGet\Install-Package LuminPack -Version 1.0.4
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="LuminPack" Version="1.0.4" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="LuminPack" Version="1.0.4" />
                    
Directory.Packages.props
<PackageReference Include="LuminPack" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add LuminPack --version 1.0.4
                    
#r "nuget: LuminPack, 1.0.4"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package LuminPack@1.0.4
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=LuminPack&version=1.0.4
                    
Install as a Cake Addin
#tool nuget:?package=LuminPack&version=1.0.4
                    
Install as a Cake Tool

LuminPack

⚡ 性能基准测试

测试环境: Windows 11 (25H2) | Intel Core i7-13650HX @ 2.60GHz | .NET 10.0

📊 性能对比图表

LuminPack Performance Benchmark

🎯 性能数据总结

二进制序列化性能对比

测试场景 LuminPack MemoryPack MessagePack 性能优势
简单对象序列化 80.27 ns 294.04 ns 1,471.37 ns 3.6x ~ 18.3x
简单对象反序列化 263.86 ns 355.66 ns 3,189.42 ns 1.3x ~ 12.1x
多态对象序列化 4.638 μs 15.591 μs 77.121 μs 3.4x ~ 16.6x
多态对象反序列化 10.008 μs 17.099 μs 114.295 μs 1.7x ~ 11.4x

JSON 序列化性能对比

测试场景 LuminPack System.Text.Json Newtonsoft.Json 性能优势
JSON 序列化 18.12 μs 22.63 μs 47.11 μs 1.2x ~ 2.6x 🚀
JSON 反序列化 16.47 μs 25.82 μs 62.43 μs 1.6x ~ 3.8x 🚀

📖 简介

LuminPack是一款高性能序列化库,对于嵌套类,LuminPack比Memorypack,Messagepack快50%,200%。对于泛型类,LuminPack比Memorypack,Messagepack快400%。对于一些特殊集合,LuminPack甚至快上22倍。

LuminPack最初是专门为Unity开发的,为Unity在存档,网络等高性能场景下提供接近手写特定类解析器的性能,LuminPack性能如何快的原因也是如此。对于LuminPack的基础类型,LuminPack会直接解析写入解析器,与此同时,LuminPack大量学习并借鉴了MemoryPack对于特定于C#的内存操纵,并通过指针反射,内存映射实现更高性能。

本人没有写Readme的经验,因此本文借鉴了Memorypack的Readme写法。

除了性能,LuminPack包含MemoryPack绝大部分特性,对于仅包含LuminPack基础类型的数据,二者甚至可以互相序列化(好像不行了,没测试)。

✨ 主要特性

  • 支持现代I / O api ( ReadOnlySpan<byte> , ReadOnlySequence<byte> )
  • 更新支持至.Net10
  • 基于增量源代码生成器的代码生成
  • 同时支持二进制、Json
  • 基于非托管内存的WriterBuffer
  • 无反射
  • 多态序列化
  • 有限的版本容忍(快速/默认)和完全的版本容忍支持
  • 循环引用序列化
  • AOT友好
  • 反序列化缓存池
  • 通过增量源代码生成器支持Unity

📦 Installation 安装

  1. Nuget搜索LuminPack安装
  2. 自行导入仓库的dll文件

🔮 后续更新计划

  1. Unity编辑器扩展,可视化生成文件
  2. 内置压缩功能
  3. 内置加密功能

🚀 Quick Start 快速开始

定义要序列化的结构体或类,并用 [LuminPackable] 属性对其进行注释。

using LuminPack;

[LuminPackable]
public class Person
{
    public int Age { get; set; }
    public string Name { get; set; }
}

序列化代码将由c#源代码生成器功能生成,您可以查询生成的名为'命名空间+外层类名(如有)+ClassNameParser.g.cs'的文件查看详细代码。

调用 LuminPackSerializer.Serialize<T>/Deserialize<T> 序列化/反序列化二进制对象实例。

调用 LuminPackSerializer.SerializeJson<T>/DeserializeJson<T> 序列化/反序列化Json对象实例。

var item = new Person { Age = 18, Name = "Light" };

var buffer = LuminPackPackSerializer.Serialize(item);
var bufferJson = LuminPackPackSerializer.SerializeJson(item);
var result = LuminPackSerializer.Deserialize<Person>(buffer);
var resultJson = LuminPackSerializer.DeserializeJson<Person>(buffer);

📋 LuminPack基础类型

默认情况下,LuminPack基础类型将实现最高性能

Int, UInt, Byte, Short, UShort, Long, ULong, 
Float, Double, Char, String, Decimal, Bool, 
Enum, Struct, Class, List, Array

🔧 内置支持的类型

默认情况下,这些类型可以被序列化:

  • .Net所有非托管类型 (byte, int, bool, char, double, etc.)
  • string, decimal, Half, Int128, UInt128, Guid, Rune, BigInteger
  • TimeSpan, DateTime, DateTimeOffset, TimeOnly, DateOnly, TimeZoneInfo
  • Complex, Plane, Quaternion Matrix3x2, Matrix4x4, Vector2, Vector3, Vector4
  • Uri, Version, StringBuilder, Type, BitArray, CultureInfo
  • T[], T[,], T[,,], T[,,,], Memory<>, ReadOnlyMemory<>, ArraySegment<>, ReadOnlySequence<>
  • Nullable<>, Lazy<>, KeyValuePair<,>, Tuple<,...>, ValueTuple<,...>
  • List<>, LinkedList<>, Queue<>, Stack<>, HashSet<>, SortedSet<>, PriorityQueue<,>
  • Dictionary<,>, SortedList<,>, SortedDictionary<,>, ReadOnlyDictionary<,>
  • Collection<>, ReadOnlyCollection<>, ObservableCollection<>, ReadOnlyObservableCollection<>, ReadOnlyCollectionBuilder<>
  • IEnumerable<>, ICollection<>, IList<>, IReadOnlyCollection<>, IReadOnlyList<>, ISet<>
  • IDictionary<,>, IReadOnlyDictionary<,>, ILookup<,>, IGrouping<,>,
  • ConcurrentBag<>, ConcurrentQueue<>, ConcurrentStack<>, ConcurrentDictionary<,>, BlockingCollection<>
  • Immutable collections (ImmutableList<>, etc.) and interfaces (IImmutableList<>, etc.)

🎯 定义 [LuminPackable] 数据

[LuminPackable] 可以注释到任何 class , abstract class , struct , record , record structinterface 。如果类型 structrecord struct 不包含引用类型(c#非托管类型),则不使用任何直接从内存序列化/反序列化的规则,LuminPack会直接复制内存。

默认情况下, [LuminPackable] 序列化公共实例属性或字段。可以使用 [LuminPackIgnore] 删除序列化目标, [LuminPackInclude] 将私有成员提升为序列化目标。

[LuminPackable]
public class MyClass
{
    [LuminPackInclude]
    private MyClass2 myClass;
}

[LuminPackable]
public class MyClass2
{
    [LuminPackInclude]
    private int num1;
    [LuminPackIgnore]
    public long num2;
    public short num3;
    public double num4;
    public float num5;
    public string[] strings;
}

LuminPack有39条诊断规则( LuminPack001LuminPack039

LuminPack不序列化成员名或其他信息,而是按照声明的顺序序列化字段。如果类型是继承的,则按照父级→子级的内存布局顺序执行序列化。成员的顺序不能因反序列化而改变。关于模式演变,请参阅版本容忍部分。

默认的序列化顺序和布局是按照声明顺序的,如果想要更改,您可以使用 [LuminPackOrder()]

注:对于循环引用和完全版本容忍模式,每个字段和属性必须注释[LuminPackOrder()]

// serialize Prop0 -> Prop1
[LuminPackable]
public class MyClass
{
    [LuminPackOrder(1)]
    public int Prop1 { get; set; }
    [LuminPackOrder(0)]
    public int Prop0 { get; set; }
}

LuminPack不依赖构造函数反序列化,因此您可以随意定义构造函数。

LuminPack默认支持 0 ~ 249 个成员字段

🔍 [LuminPackableObject]

[LuminPackableObject]可以作用于任何字段以及属性,这将告诉LuminPackCodeGenerator不要直接解析该字段并写入Myclass的解析器,而是通过注册在LuminPack的Myclass2的解析器去解析。通常情况下,这会损失大概30%的性能,因此如果您遇到源代码生成器生成错误代码等情况,可以尝试用[LuminPackableObject]标记字段或属性。

以LuminPackable的示例代码为例。在 .Net8 以上的平台, 对于嵌套类私有字段的解析,注释[LuminPackInclude]将会 正常工作。但在 .Net Standard2.1 平台,这将不会工作

例如以上示例代码,对于MyClass的 "private MyClass2 myClass;" 字段,使用[LuminPackInclude]将会正常工作并解析MyClass2的所有 public 字段,但是不会解析Myclass2的 private 字段, 即使您在Myclass2的 private 字段标记[LuminPackInclude]。如果想要正常工作,请使用[LuminPackableObject]来取消基础类型的解析。

[LuminPackable]
public class MyClass
{
    [LuminPackInclude]
    [LuminPackableObject] //这将使MyClass2的私有字段num1正常解析
    private MyClass2 myClass;
}

序列化回调

在实例方法或静态方法标记[LuminPackOnSerialized],[LuminPackOnSerializing],[LuminPackOnDeserialized],[LuminPackOnDeserializing]等特性

[LuminPackOnSerializing]
public static void OnSerializing()
{
    Console.WriteLine("OnSerializing");
}
    
[LuminPackOnSerialized]
public void OnSerialized()
{
    Console.WriteLine("OnSerialized");
}
    
[LuminPackOnDeserialized]
public void OnDeserialized()
{
    Console.WriteLine("OnDeserialized");
}
    
[LuminPackOnDeserializing]
public static void OnDeserializing()
{
    Console.WriteLine("OnDeserializing");
}

🔄 反序列化缓存池

LuminPack支持反序列化从缓存池取代new创建实例,减少GC开销。

LuminPack并不实现缓存池逻辑,需要用户自行实现,同时用户也应该注意Return实例,LuminPack并不追踪对象去Return

要启用缓存池,需要在静态方法上标记 [LuminPackPoolRent] 特性

[LuminPackPoolRent]
public static SimpleClass Rent()
{
    return this.Pool.Rent();
}

🎭 多态序列化

LuminPack支持序列化接口和抽象类对象,实现多态序列化。

LuminPack支持最低程度的自动收集继承类,对于标记了[LuminPackable]特性的abstract,interface,LuminPack会自动收集符合以下规则的子类

规则:
1. 如果子类泛型参数数量超过基类,不收集
2. 如果子类泛型参数不是直接传递给基类(如 MyClass<U> : MyClassBase<T>),不收集
3. 如果子类有约束且和基类约束不一样,不收集
4. 如果基类被完全具象化(如 MyClassBase<int>),子类不能有泛型参数

允许的情况:
- MyClass<T> : MyClassBase<T> (泛型参数完全匹配,约束一致)
- MyClass : MyClassBase<int> (完全具体化,子类无泛型)

Id分配规则:从0开始递个递增,如果遇到[LuminPackUnion]显示注册过的Id,则跳过。

对于不收集的类型,LuminPack支持手动注册。与MemoryPack的Union相同。只有接口和抽象类允许使用 [LuminPackUnion] 属性进行注释。需要唯一的联合标记。

// Annotate [LuminPackable] and inheritance types with [LuminPackUnion]
// Union also supports interface class
[LuminPackable]
[LuminPackUnion(0, typeof(Child1))]
[LuminPackUnion(1, typeof(Child2))]
public abstract class IUnionSample
{
}

[LuminPackable]
public class Child1 : IUnionSample
{
    public int num;
}

[LuminPackable]
public class Child2 : IUnionSample
{
    public string str;
}

IUnionSample data = new Child1() { num = 114514};

// Serialize
var buffer = LuminPackSerializer.Serialize(data);

// Deserialize
var result = LuminPackSerializer.Deserialize<IUnionSample>(buffer);

switch (result)
{
    case Child1 x:
        Console.WriteLine(x.num);
        break;
    case Child2 x:
        Console.WriteLine(x.str);
        break;
    default:
        break;
}

对于LuminPackUnion的Tag,支持 0 ~ 65535, 对与250以下的性能更佳。因此推荐使用250以下的值作为Tag

🌐 跨程序集多态

LuminPack支持跨程序集多态序列化。如果程序集A定义了abstract类,程序集B的类继承了程序集A的abstract类,由于源生成器的限制,源生成器并不能分析到程序集B继承的子类,不会生成对应的序列化代码。此时需要用户手动注册,调用源生成器为A生成的abstract的Parser类的Register方法。以下是示例代码

global::LuminPack.Generated.LuminPackBenchmark_SimpleClassBaseParser.Register(); 
//所有生成的Parser都在LuminPack.Generated命名空间下,生成的Parser的类名规则为:命名空间+外层类名+类名+Parser

Register方法接受6个参数,分别是Type,Id,二进制序列化函数指针,二进制反序列化函数指针,Json序列化函数指针,Json反序列化函数指针

用户需要手写几个静态函数

private static unsafe void WriteLuminPackBenchmark_FooA(ref LuminPackWriter writer, ref global::LuminPackBenchmark.IFoo value)
{
    writer.WriteUnionHeader(0);
    writer.WritePolymorphismValue(LuminPackMarshal.As<global::LuminPackBenchmark.IFoo, global::LuminPackBenchmark.FooA>(ref value));
}

private static unsafe void ReadLuminPackBenchmark_FooA(ref LuminPackReader reader, ref global::LuminPackBenchmark.IFoo value)
{
    global::LuminPackBenchmark.FooA tempValue = default!;
    reader.ReadPolymorphismValue(ref tempValue);
    value = LuminPackMarshal.As<global::LuminPackBenchmark.FooA, global::LuminPackBenchmark.IFoo>(ref tempValue!);
}

private static unsafe void WriteJsonLuminPackBenchmark_FooA(ref global::LuminPack.Core.LuminPackJsonWriter writer, ref global::LuminPackBenchmark.IFoo value)
{
    writer.WriteObjectStart();
    writer.WritePropertyName(LuminPackConstUtf8.TypeU8);
    writer.WriteInt(0);
    writer.WritePropertyName(LuminPackConstUtf8.ValueU8);
    writer.WriteValue(ref LuminPackMarshal.As<global::LuminPackBenchmark.IFoo, global::LuminPackBenchmark.FooA>(ref value)!);
    writer.WriteObjectEnd();
}

private static unsafe void ReadJsonLuminPackBenchmark_FooA(ref global::LuminPack.Core.LuminPackJsonReader reader, ref global::LuminPackBenchmark.IFoo value)
{
    global::LuminPackBenchmark.FooA tempValue = default!;
    reader.ReadValue(ref tempValue);
    value = LuminPackMarshal.As<global::LuminPackBenchmark.FooA, global::LuminPackBenchmark.IFoo>(ref tempValue!);
}

// 最后调用Register方法
LuminPack.Generated.LuminPackBenchmark_IFooParser.Register(
    typeof(FooA), 100, 
    &WriteLuminPackBenchmark_FooA, 
    &ReadLuminPackBenchmark_FooA, 
    &WriteJsonLuminPackBenchmark_FooA, 
    &ReadJsonLuminPackBenchmark_FooA);

📝 版本容忍

在默认情况下 LuminPack的代码生成模式( GenerateType.Object ), 仅支持有限的模式演化。

  • 如果数据类型是非托管数据,例如Struct(不包含引用类型)。不能更改数据
  • 可以添加成员,不能删除成员。
  • 不能更改成员名称
  • 不能更改成员顺序
  • 不能更改成员类型
[LuminPackable]
public class MyClass
{
    public int Prop1 { get; set; }
    public long Prop2 { get; set; }
}

// Add is OK.
[LuminPackable]
public class MyClass
{
    public int Prop1 { get; set; }
    public long Prop2 { get; set; }
    public int? AddedProp { get; set; }
}

// Remove is NG.
[LuminPackable]
public class MyClass
{
    // public int Prop1 { get; set; }
    public long Prop2 { get; set; }
}

// Change order is NG.
[LuminPackable]
public class MyClass
{
    public long Prop2 { get; set; }
    public int Prop1 { get; set; }
}

当使用 GenerateType.VersionTolerant 时,它支持完全的版本容忍。

[LuminPackable(GenerateType.VersionTolerant)]
public class VersionTolerantObject1
{
    [LuminPackOrder(0)]
    public int MyProperty0 { get; set; } = default;

    [LuminPackOrder(1)]
    public long MyProperty1 { get; set; } = default;

    [LuminPackOrder(2)]
    public short MyProperty2 { get; set; } = default;
}

[LuminPackable(GenerateType.VersionTolerant)]
public class VersionTolerantObject2
{
    [LuminPackOrder(0)]
    public int MyProperty0 { get; set; } = default;

    // deleted
    //[LuminPackOrder(1)]
    //public long MyProperty1 { get; set; } = default;

    [LuminPackOrder(2)]
    public short MyProperty2 { get; set; } = default;

    // added
    [LuminPackOrder(3)]
    public short MyProperty3 { get; set; } = default;
}

GenerateType.VersionTolerantGenerateType.Object 性能更差,使用时请注意。

🔗 循环引用

// to enable circular-reference, use GenerateType.CircularReference
[LuminPackable(GenerateType.CircularReference)]
public class Node
{
    [LuminPackOrder(0)]
    public Node? Parent { get; set; }
    [LuminPackOrder(1)]
    public Node[]? Children { get; set; }
}

GenerateType.CircularReference 具有与版本容忍相同的特性。

对象引用跟踪只对标记为 GenerateType.CircularReference 的对象进行。如果要跟踪任何其他对象,请对其进行包装。

💾 WriteBuffer池

LuminPack的序列化池通过Marshal申请非托管内存,这极大提高了Buffer扩容的性能。

因此,请确保所有WriteBuffer调用Dispose方法,以释放非托管内存。

LuminPack内置了高性能ObjectPool

#if NET8_0_OR_GREATER
    private static readonly ObjectPool<ReusableLinkedArrayBufferWriter> _pool = 
        new(MaxPoolSize);
#else
    private static readonly ObjectPool<ReusableLinkedArrayBufferWriter> _pool = 
        new(new BufferWriterPolicy(), MaxPoolSize);
#endif

public static ReusableLinkedArrayBufferWriter Rent() => _pool.Rent();
    
public static void Return(ReusableLinkedArrayBufferWriter writer) => _pool.Return(writer);

.Net8以上版本,ObjectPool的对象需要继承IPooledObjectPolicy接口。

.Net Standard2.1版本,则需要单独定义继承继承IPooledObjectPolicy接口的类,通过依赖注入的方式。

🎮 Unity

LuminPack对于Unity有特殊优化,以达到.Net8版本相同的性能。

  • 对于List<>,Stack<>,Queue<>,Collection<>,ReadonlyCollection<>,ObserveableCollection<>,ReadonlyObserveableCollection<>,ReadOnlyCollectionBuilder<> 的非托管泛型,LuminPack比MemoryPack快22倍 (1024数据量)
  • Serialize API和Deserialize API的类型检查优化,提高处理特殊数据的性能。

📐 二进制格式规范

端序必须 Little Endian

非托管结构

非托管结构是不包含引用类型的c#结构,类似于c#非托管类型的约束。序列化结构布局,包括填充。

Object 对象

(byte memberCount, [values...])

对象头文件中的成员计数为1字节无符号字节。成员数允许 0249 , 255 表示对象 null 。值存储成员数的内存包值。

Version Tolerant Object 版本容忍对象

(byte memberCount, [varint byte-length-of-values...], [values...])

版本容忍对象与 Object 类似,但在头部包含值的字节长度。变长整数遵循以下规范:第一个有符号字节(sbyte)是值或类型代码,接下来的 X 个字节是具体值。其中,0 到 127 对应无符号字节值,-1 到 - 120 对应有符号字节值,-121 对应字节(byte),-122 对应有符号字节(sbyte),-123 对应无符号短整数(ushort),-124 对应短整数(short),-125 对应无符号整数(uint),-126 对应整数(int),-127 对应无符号长整数(ulong),-128 对应长整数(long)。

Circular Reference Object 循环引用对象

(byte memberCount, [varint byte-length-of-values...], varint referenceId, [values...])
(250, varint referenceId)

循环引用对象类似于版本容忍对象,但如果memberCount为250,则下一个变量(unsigned-int32)为referenceId。如果不是,则在字节长度值之后写入变量referenceId。

String 字符串

(int utf16-length, utf16-value)
(int ~utf8-byte-count, int utf16-length, utf8-bytes)

字符串有两种形式,UTF16和UTF8。如果第一个4byte有符号整数 -1 ,表示null。 0 ,表示空。UTF16与collection相同(序列化为 ReadOnlySpan<char> , UTF16 -value的字节数为UTF16 -length * 2)。如果第一个有符号整数<= -2 ,则value用UTF8编码。Utf8-byte-count以补码形式编码, ~utf8-byte-count 检索字节数。下一个有符号整数是utf16-length,它允许 -1 表示未知长度。Utf8-bytes存储utf8-byte-count的字节数。

Union 多态

(byte tag, value)
(250, ushort tag, value)

第一个无符号字节是用于区分值类型或标志的标记, 0249 表示标记, 250 表示下一个无符号短标记, 255 表示 null

Collection 集合

(int length, [values...])

集合头的数据计数为4字节有符号整数, -1 表示 null 。头字节存储数据长度。

Tuple 元组

(values...)

元组是固定大小的非空值集合。 KeyValuePair<TKey, TValue>ValueTuple<T,...> 被序列化为Tuple。

📄 License 许可证

This library is licensed under the MIT License.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.4 86 2/26/2026
1.0.3 81 2/26/2026
1.0.2 91 1/17/2026
1.0.1 89 1/17/2026
1.0.0 95 1/17/2026