前阵子看到MemoryPack
序列化库,加上要做帧同步校验/回滚,决定照着抄了一个精简版序列化工具。基于IBufferWritere<byte>
、ReadOnlySpan<byte>
、ReadOnlySequence<byte>
(未实现,比较复杂)等现代 .NET IO Api,并配合SourceGenerator代码生成,实现高效的零反射
序列化,并且无缝兼容unity aot环境。
特性
- 支持unmanaged、class、array、list多种类型
- 引用序列化,引用类型(容器除外),全部基于引用来序列化,适应复杂oop逻辑
- 容器类支持,List、HashSet、Dictionary等
- 多态序列化,通过编写简单代码,可以轻松支持Interface/Abstract Class,并且基类、子类可跨程序集
- 多种序列化事件,可以穿插自定义逻辑
- 可dump字符串,方便debug
性能
测试结果
| Method | BufferCount | Mean | Error | Allocated |
|--------------------- |------------ |----------------:|------:|----------:|
| MemoryPack_Serialize | 10 | 63.75 ns | NA | - |
| FSnapshot_Serialize | 10 | 32.96 ns | NA | - |
| Normal_Serialize | 10 | 56.73 ns | NA | - |
| MemoryPack_Serialize | 100 | 91.63 ns | NA | - |
| FSnapshot_Serialize | 100 | 61.32 ns | NA | - |
| Normal_Serialize | 100 | 542.56 ns | NA | - |
| MemoryPack_Serialize | 1000 | 349.83 ns | NA | - |
| FSnapshot_Serialize | 1000 | 320.90 ns | NA | - |
| Normal_Serialize | 1000 | 5,260.09 ns | NA | - |
| MemoryPack_Serialize | 10000 | 3,916.05 ns | NA | - |
| FSnapshot_Serialize | 10000 | 3,853.95 ns | NA | - |
| Normal_Serialize | 10000 | 52,770.45 ns | NA | - |
| MemoryPack_Serialize | 65536 | 30,041.41 ns | NA | - |
| FSnapshot_Serialize | 65536 | 29,814.07 ns | NA | - |
| Normal_Serialize | 65536 | 347,519.04 ns | NA | - |
| MemoryPack_Serialize | 100000 | 44,204.24 ns | NA | - |
| FSnapshot_Serialize | 100000 | 45,657.86 ns | NA | 1 B |
| Normal_Serialize | 100000 | 535,551.86 ns | NA | 1 B |
| MemoryPack_Serialize | 1000000 | 1,479,738.87 ns | NA | 6 B |
| FSnapshot_Serialize | 1000000 | 1,379,992.19 ns | NA | 1 B |
| Normal_Serialize | 1000000 | 5,692,993.75 ns | NA | 5 B |
测试代码
[Benchmark]
public void MemoryPack_Serialize()
{
MemoryPackSerializer.Serialize(bufferWriter, array_1);
bufferWriter.Clear();
}
[Benchmark]
public void FSnapshot_Serialize()
{
using var writer = new FSnapshotWriter(bufferWriter);
writer.WriteUnmanagedArray(array_1);
bufferWriter.Clear();
}
[Benchmark]
public void Normal_Serialize()
{
var size = Unsafe.SizeOf<Vector3>();
for (int i = 0; i < array_1.Length; i++)
{
var v = array_1[i];
var span = bufferWriter.GetSpan(size);
ref var dst = ref MemoryMarshal.GetReference(span);
Unsafe.WriteUnaligned(ref dst, v.X);
Unsafe.WriteUnaligned(ref Unsafe.Add(ref dst, Unsafe.SizeOf<float>()), v.Y);
Unsafe.WriteUnaligned(ref Unsafe.Add(ref dst, 2 * Unsafe.SizeOf<float>()), v.Z);
bufferWriter.Advance(size);
}
bufferWriter.Clear();
}
例子
序列化类
using System;
using System.Collections.Generic;
using Saro.FSnapshot;
namespace App;
[FSnapshotUnionFormatter(typeof(Animal))]
[FSnapshotUnion(0, typeof(Dog))]
[FSnapshotUnion(1, typeof(Rabbit))]
public partial class AnimalUnionFormatter
{ }
[FSnapshotable]
[Serializable]
public partial class Animals
{
public enum EType
{
Type0 = 0,
Type1 = 1,
}
[FSnapshot] public EType type = EType.Type1;
[FSnapshot] public List<Animal> animals_list;
[FSnapshot] public Animal[] animals_array;
}
[FSnapshotable]
[Serializable]
public abstract partial class Animal
{
[FSnapshot] public string baseText;
}
[FSnapshotable]
[Serializable]
public partial class Dog : Animal
{
[FSnapshot] public string dog;
[FSnapshot] public List<int> int_list;
[FSnapshot] public int[] int_array;
[FSnapshot] public int? int_nullable; // 8b
[FSnapshot] public Nullable<byte> int_nullable_null; // 2b
}
[FSnapshotable]
[Serializable]
public partial class Rabbit : Animal
{
[FSnapshot] public string rabbit;
[FSnapshot] public List<(int, bool)> tuple_list;
[FSnapshot] public HashSet<int> set;
[FSnapshot] public Dictionary<int, long> dic;
[FSnapshot] public Dictionary<string, string> dic_obj;
[FSnapshotOnSerializing]
void OnSerializing(ref FSnapshotWriter writer)
{
writer.WriteString("header");
}
[FSnapshotOnSerialized] void OnSerialized() { }
[FSnapshotOnDeserializing]
void OnDeserializing(ref FSnapshotReader reader)
{
var header = reader.ReadString();
}
[FSnapshotOnDeserialized] void OnDeserialized() { }
}
使用方式
using System;
using System.Buffers;
using System.Collections.Generic;
using Saro.FSnapshot;
using App;
using System.Text;
Animals CreateObj()
{
var dog = new Dog
{
baseText = "animal",
dog = "dog",
int_array = new int[] { 1, 2, 3 },
int_list = new List<int> { 11, 22, 33 },
int_nullable = 999,
};
var rabbit = new Rabbit
{
baseText = "animal",
rabbit = "rabbit",
tuple_list = new List<(int, bool)> { (1, true), (2, false) },
set = new HashSet<int>() { 3, 2, 1 },
dic = new Dictionary<int, long> { { 1, 1 }, { 2, 2 }, { 3, 3 } },
dic_obj = new Dictionary<string, string> { { "a", "a" }, { "b", "b" }, { "c", "c" } },
};
var animals = new Animals();
animals.animals_array = new Animal[]
{
dog,
rabbit,
};
animals.animals_list = new List<Animal>(animals.animals_array);
animals.animals_list.Reverse();
return animals;
}
void Test2()
{
var bufferWriter = new ArrayBufferWriter<byte>(2048);
// create obj
var animals = CreateObj();
// take snapshot
{
using var writer = new FSnapshotWriter(bufferWriter);
writer.WriteObject(animals);
DumpJson("animals:", animals);
}
// resotre snapshot
{
Animals newAnimals = null;
using var reader = new FSnapshotReader(bufferWriter.WrittenSpan);
reader.ReadObject(ref newAnimals);
DumpJson("newAnimals:", newAnimals);
DumpText("newAnimals:", newAnimals);
// DumpRaw("newAnimals:", newAnimals);
}
}
Test2();
/*
OUTPUT
animals:
{"type":1,"animals_list":[{"$type":"App.Rabbit, ConsoleApp","rabbit":"rabbit","tuple_list":[{"Item1":1,"Item2":true},{"Item1":2,"Item2":false}],"set":[3,2,1],"dic":{"1":1,"2":2,"3":3},"dic_obj":{"a":"a","b":"b","c":"c"},"baseText":"animal"},{"$type":"App.Dog, ConsoleApp","dog":"dog","int_list":[11,22,33],"int_array":[1,2,3],"int_nullable":999,"int_nullable_null":null,"baseText":"animal"}],"animals_array":[{"$type":"App.Dog, ConsoleApp","dog":"dog","int_list":[11,22,33],"int_array":[1,2,3],"int_nullable":999,"int_nullable_null":null,"baseText":"animal"},{"$type":"App.Rabbit, ConsoleApp","rabbit":"rabbit","tuple_list":[{"Item1":1,"Item2":true},{"Item1":2,"Item2":false}],"set":[3,2,1],"dic":{"1":1,"2":2,"3":3},"dic_obj":{"a":"a","b":"b","c":"c"},"baseText":"animal"}]}
newAnimals:
{"type":1,"animals_list":[{"$type":"App.Rabbit, ConsoleApp","rabbit":"rabbit","tuple_list":[{"Item1":1,"Item2":true},{"Item1":2,"Item2":false}],"set":[3,2,1],"dic":{"1":1,"2":2,"3":3},"dic_obj":{"a":"a","b":"b","c":"c"},"baseText":"animal"},{"$type":"App.Dog, ConsoleApp","dog":"dog","int_list":[11,22,33],"int_array":[1,2,3],"int_nullable":999,"int_nullable_null":null,"baseText":"animal"}],"animals_array":[{"$type":"App.Dog, ConsoleApp","dog":"dog","int_list":[11,22,33],"int_array":[1,2,3],"int_nullable":999,"int_nullable_null":null,"baseText":"animal"},{"$type":"App.Rabbit, ConsoleApp","rabbit":"rabbit","tuple_list":[{"Item1":1,"Item2":true},{"Item1":2,"Item2":false}],"set":[3,2,1],"dic":{"1":1,"2":2,"3":3},"dic_obj":{"a":"a","b":"b","c":"c"},"baseText":"animal"}]}
newAnimals:
$type:Animals
$src:0
Type1
[
$union:1
$type:Rabbit
$src:1
animal
rabbit
[(1, True),(2, False)]
[
3
2
1
]
{
[1, 1]
[2, 2]
[3, 3]
}
{
[
a
,
a
]
[
b
,
b
]
[
c
,
c
]
}
$union:0
$type:Dog
$src:2
animal
dog
[11,22,33]
[1,2,3]
999
null
]
[
$ref:2
$ref:1
]
*/