快速序列化组件MessagePack介绍
MessagePack for C#(MessagePack-CSharp)是用于C#的极速MessagePack序列化程序,比MsgPack-Cli快10倍,与其他所有C#序列化程序相比,具有最好的性能。 MessagePack for C#具有内置的LZ4压缩功能,可以实现超快速序列化和二进制占用空间小。 性能永远是重要的! 可用于游戏,分布式计算,微服务,数据存储到Redis等。支持.NET, .NET Core, Unity, Xamarin。
从上图我们看出MessagePack for C#在性能测试中是最好的,这里解释一下第三个MsgPack-Cli是MessagePack官方实现的。第一和第二都是MessagePack for C#,第一项相比第二项具有稍快一点的序列化和反序列化速度,但是第二项采用了L4压缩功能,显著的减少了二进制的大小。在实际使用中推荐使用L4压缩功能。
使用
定义一个类添加[MessagePackObject]
特性,公共成员(属性或者字段)添加[Key]
特性,调用MessagePackSerializer.Serialize/Deserialize
进行序列化和反序列化,ToJson
可以帮我们转储二进制为json格式。
[MessagePackObject ]
public class MyClass
{
[Key(0) ]
public int Age { get ; set ; }
[Key(1) ]
public string FirstName { get ; set ; }
[Key(2) ]
public string LastName { get ; set ; }
[IgnoreMember ]
public string FullName { get { return FirstName + LastName; } }
}
class Program
{
static void Main (string [] args )
{
var mc = new MyClass
{
Age = 99 ,
FirstName = "hoge" ,
LastName = "huga" ,
};
var bytes = MessagePackSerializer.Serialize(mc);
var mc2 = MessagePackSerializer.Deserialize(bytes);
var json = MessagePackSerializer.ToJson(bytes);
Console.WriteLine(json);
Console.ReadKey();
}
}
序列化索引将会影响该信息在序列化数据中的位置
默认情况下特性是必须的,但是我们有方法进行改变,让它变为不是必须的,详情请看后面。
分析器
这些类型可以默认序列化。
基元(int、string等等), Enum, Nullable<>, TimeSpan, DateTime, DateTimeOffset, Nil, Guid, Uri, Version, StringBuilder, BitArray, ArraySegment<>, BigInteger, Complext, Task, Array[], Array[,], Array[,,], Array[,,,], KeyValuePair<,>, Tuple<,...>, ValueTuple<,...>, List<>, LinkedList<>, Queue<>, Stack<>, HashSet<>, ReadOnlyCollection<>, IList<>, ICollection<>, IEnumerable<>, Dictionary<,>, IDictionary<,>, SortedDictionary<,>, SortedList<,>, ILookup<,>, IGrouping<,>, ObservableCollection<>, ReadOnlyOnservableCollection<>, IReadOnlyList<>, IReadOnlyCollection<>, ISet<>, ConcurrentBag<>, ConcurrentQueue<>, ConcurrentStack<>, ReadOnlyDictionary<,>, IReadOnlyDictionary<,>, ConcurrentDictionary<,>, Lazy<>, Task<>, 自定义继承ICollection <>或IDictionary <,>具有无参构造方法, IList,IDictionary和自定义继承ICollection或IDictionary具有无参构造函数(包括ArrayList和Hashtable)。
您可以添加自定义类型的支持和一些官方/第三方扩展包。 对于ImmutableCollections(ImmutableList <>等),对于ReactiveProperty和Unity(Vector3, Quaternion等等),对于F#(Record,FsList,Discriminated Unions等)。
MessagePack.Nil
是MessagePack for C#的内置null/void/unit表示类型。
对象序列化
您可以使用[DataContract]而不是[MessagePackObject]。 如果type标记为DataContract,则可以使用[DataMember]代替[Key],[IgnoreDataMember]代替[IgnoreMember]。
[DataMember(Order = int)] 和 [Key(int)]相同, [DataMember(Name = string)]和 [Key(string)]相同. 如果使用 [DataMember], 则类似于 [Key(nameof(propertyname)].
使用DataContract使其成为一个共享的类库,您不必引用MessagePack for C#。 但是,它不包含在分析器或由mpc.exe
生成的代码中。此外,像UnionAttribute,MessagePackFormatterAttribute,SerializationConstructorAttribute等功能不能使用。 出于这个原因,我建议您基本上使用MessagePack for C#特性。
序列化不可变对象(序列化构造器)
如果对象实现了IMessagePackSerializationCallbackReceiver
,则接受OnBeforeSerialize
和OnAfterDeserialize
序列化处理。
[MessagePackObject ]
public class SampleCallback : IMessagePackSerializationCallbackReceiver
{
[Key(0) ]
public int Key { get ; set ; }
public void OnBeforeSerialize ()
{
Console.WriteLine("OnBefore" );
}
public void OnAfterDeserialize ()
{
Console.WriteLine("OnAfter" );
}
}
Union
如果使用MessagePackSerializer.Deserialize 或者 MessagePackSerializer.Deserialize
,messagepack将转换为 primitive values,msgpack-primitive将转换为 bool, char, sbyte, byte, short, int, long, ushort, uint, ulong, float, double, DateTime, string, byte[], object[], IDictionary
.
var model = new DynamicModel { Name = "foobar" , Items = new [] { 1 , 10 , 100 , 1000 } };
var bin = MessagePackSerializer.Serialize(model, ContractlessStandardResolver.Instance);
var dynamicModel = MessagePackSerializer.Deserialize<dynamic >(bin, ContractlessStandardResolver.Instance);
Console.WriteLine(dynamicModel["Name" ]);
Console.WriteLine(dynamicModel["Items" ][2 ]);
所以你可以使用索引访问键值对或者数组。
Object 类型序列化
Typeless API就像BinaryFormatter, 将类型信息嵌入到二进制中,所以不需要类型去反序列化.
object mc = new Sandbox.MyClass()
{
Age = 10 ,
FirstName = "hoge" ,
LastName = "huga"
};
var bin = MessagePackSerializer.Typeless.Serialize(mc);
Console.WriteLine(MessagePackSerializer.ToJson(bin));
var objModel = MessagePackSerializer.Typeless.Deserialize(bin) as MyClass;
类型信息由mspgack ext格式序列化,typecode为100。
MessagePackSerializer.Typeless
是Serialize / Deserialize (TypelessContractlessStandardResolver.Instance)的快捷方式。 如果要配置默认的Typeless解析器,可以通过MessagePackSerializer.Typeless.RegisterDefaultResolver
进行设置。
性能
性能取决于选项。 这是一个BenchmarkDotNet的微型benchamark。 目标对象有9个成员(MyProperty1?MyProperty9),值为零。
Method Mean Error Scaled Gen 0 Allocated
IntKey
72.67 ns
NA
1.00
0.0132
56 B
StringKey
217.95 ns
NA
3.00
0.0131
56 B
Typeless_IntKey
176.71 ns
NA
2.43
0.0131
56 B
Typeless_StringKey
378.64 ns
NA
5.21
0.0129
56 B
MsgPackCliMap
1,355.26 ns
NA
18.65
0.1431
608 B
MsgPackCliArray
455.28 ns
NA
6.26
0.0415
176 B
ProtobufNet
265.85 ns
NA
3.66
0.0319
136 B
Hyperion
366.47 ns
NA
5.04
0.0949
400 B
JsonNetString
2,783.39 ns
NA
38.30
0.6790
2864 B
JsonNetStreamReader
3,297.90 ns
NA
45.38
1.4267
6000 B
JilString
553.65 ns
NA
7.62
0.0362
152 B
JilStreamReader
1,408.46 ns
NA
19.38
0.8450
3552 B
IntKey,StringKey,Typeless_IntKey,Typeless_StringKey都是MessagePack for C#的方法
,在反序列化过程中实现零内存分配。JsonNetString /JilString从字符串反序列化。JsonStStreamReader / JilStreamReader是从StreamReader的UTF8 byte []中反序列化的。反序列化通常从Stream读取。 因此,它将从字节数组(或流)而不是字符串中读取。
MessagePack for C#IntKey是最快的。 StringKey比IntKey慢,因为StringKey需要从字符串进行匹配。 如果是IntKey,读取数组长度,根据数组长度进行for循环二进制解码。 如果StringKey,读取map 长度,根据map长度循环,首先需要对密钥解码,然后按照key查找,最后二进制解码,则需要额外两个步骤(解码密钥和按键查找)。
字符串键通常是有用的,无约束的,简单的JSON替换,与其他语言的互操作性,以及更多的某些版本。 MessagePack for C#也为String Key进行了优化。 首先,它不会将UTF8字节数组解码为与成员名称匹配的字符串,它会按原样查找字节数组(避免解码成本和额外分配)。
它会尝试匹配每个长整型(long)(每8个字符,如果长度不够,填充0)使用automata和在生成时内联IL代码。
这也避免了计算字节数组的哈希码,并且可以在长单元上进行多次比较。
这是ILSpy生成的反序列化器代码的示例的反编译。
https://github.com/neuecc/MessagePack-CSharp#performance
如果节点数量很大,则使用嵌入式二进制搜索进行搜索。
另外请注意,这是序列化的基准测试结果。
Method Mean Error Scaled Gen 0 Allocated
IntKey
84.11 ns
NA
1.00
0.0094
40 B
StringKey
126.75 ns
NA
1.51
0.0341
144 B
Typeless_IntKey
183.31 ns
NA
2.18
0.0265
112 B
Typeless_StringKey
193.95 ns
NA
2.31
0.0513
216 B
MsgPackCliMap
967.68 ns
NA
11.51
0.1297
552 B
MsgPackCliArray
284.20 ns
NA
3.38
0.1006
424 B
ProtobufNet
176.43 ns
NA
2.10
0.0665
280 B
Hyperion
280.14 ns
NA
3.33
0.1674
704 B
ZeroFormatter
149.95 ns
NA
1.78
0.1009
424 B
JsonNetString
1,432.55 ns
NA
17.03
0.4616
1944 B
JsonNetStreamWriter
1,775.72 ns
NA
21.11
1.5526
6522 B
JilString
547.51 ns
NA
6.51
0.3481
1464 B
JilStreamWriter
778.78 ns
NA
9.26
1.4448
6066 B
当然,IntKey是最快的,但StringKey也不错。
LZ4压缩
protbuf-net是.NET上最常用的二进制格式化库。 我(作者)喜欢protobuf-net,并尊重那伟大的工作。 但是如果使用protobuf-net作为通用序列化格式,则可能会引起烦人的问题。
[ProtoContract ]
public class Parent
{
[ProtoMember(1) ]
public int Primitive { get ; set ; }
[ProtoMember(2) ]
public Child Prop { get ; set ; }
[ProtoMember(3) ]
public int [] Array { get ; set ; }
}
[ProtoContract ]
public class Child
{
[ProtoMember(1) ]
public int Number { get ; set ; }
}
using (var ms = new MemoryStream())
{
ProtoBuf.Serializer.Serialize(ms, null );
ms.Position = 0 ;
var result = ProtoBuf.Serializer.Deserialize(ms);
Console.WriteLine(result != null );
Console.WriteLine(result.Primitive);
Console.WriteLine(result.Prop);
Console.WriteLine(result.Array);
}
using (var ms = new MemoryStream())
{
ProtoBuf.Serializer.Serialize(ms, new Parent { Array = new int [0 ] });
ms.Position = 0 ;
var result = ProtoBuf.Serializer.Deserialize(ms);
Console.WriteLine(result.Array == null );
}
protobuf(-net)不能正确处理null和空集合。 因为protobuf没有null表示(这是protobuf-net作者的答案)。
MessagePack规范可以完全序列化C#类型。 这就是推荐MessagePack而不是protobuf的原因。
Protocol Buffers具有良好的IDL和gRPC,这比MessagePack好得多。 如果你想使用IDL,我(作者)推荐Google.Protobuf。
JSON是很好的通用格式。 这是完美的,简单的,足够规范的。 Utf8Json创建了我采用与MessagePack for C#相同的体系结构,并避免编码/修饰成本,所以像二进制一样工作。 如果你想了解二进制与文本,请参阅Utf8Json /应使用哪个序列化器部分。
ZeroFormatter与FlatBuffers类似,但专门用于C#。 这是特别的。 反序列化速度非常快,但是二进制大小却很大。 而ZeroFormatter的缓存算法需要额外的内存。
ZeroFormatter也是特别的。 当与ZeroFormatter对比的情况下,它显示格式化的力量。 但是对于许多常见的用途,MessagePack for C#会更好。
扩展
MessagePack for C#具有扩展点,您可以添加外部类型的序列化支持。 下列是官方扩展支持。
Install -Package MessagePack.ImmutableCollection
Install -Package MessagePack.ReactiveProperty
Install -Package MessagePack.UnityShims
Install -Package MessagePack.AspNetCoreMvcFormatter
MessagePack.ImmutableCollection
添加对 System.Collections.Immutable
的支持. 添加了对ImmutableArray<>, ImmutableList<>, ImmutableDictionary<,>, ImmutableHashSet<>, ImmutableSortedDictionary<,>, ImmutableSortedSet<>, ImmutableQueue<>, ImmutableStack<>, IImmutableList<>, IImmutableDictionary<,>, IImmutableQueue<>, IImmutableSet<>, IImmutableStack<>
的序列化支持.
MessagePack.ReactiveProperty包添加对ReactiveProperty库的支持。它增加了ReactiveProperty <>,IReactiveProperty <>,IReadOnlyReactiveProperty <>,ReactiveCollection <>,unit
序列化支持。 这对保存视图模型状态很有用。
MessagePack.AspNetCoreMvcFormatter是ASP.NET Core MVC序列化的附加组件,可提升性能。 这是配置示例。
public void ConfigureServices (IServiceCollection services )
{
services.AddMvc().AddMvcOptions(option =>
{
option.OutputFormatters.Clear();
option.OutputFormatters.Add(new MessagePackOutputFormatter(ContractlessStandardResolver.Instance));
option.InputFormatters.Clear();
option.InputFormatters.Add(new MessagePackInputFormatter(ContractlessStandardResolver.Instance));
});
}
更多信息请访问github: https://github.com/neuecc/MessagePack-CSharp