[.NET 开源] 高性能的 Swifter.MessagePack 已发布,并发布新版本的 Swifter.Json 和 Swifter.Data。

抱歉各位朋友,由于各种私事公事,本应该在 19 年底发布的 Swifter.MessagePack 库延迟了这么久才发布,我深感抱歉。

MsgPack 简介

MsgPack 一种非常轻巧的二进制数据交换格式,巧妙的设计让它相比其他二进制数据格式更可读,并且有着不错的压缩率和逻辑性能,是目前相当火热的数据交换格式。

Swifter.MessagePack 遵循 MsgPack 新的规范实现;相比 .NET 其他 MsgPack 序列化库,Swifter.MessagePack 有着更好的性能,生成的内容更紧凑合理且更简单易用。

Nuget:Swifter.MessagePackSwifter.JsonSwifter.Data

GitHub:Swifter.MessagePackSwifter.Json

如果您想使用 Swifter 库,请在 Nuget 上安装/下载最新版本,如需单文件版本,请自行生成/合并。

 

简单使用 Swifter.MessagePack

MessagePackFormatter 类内部还有十个方法重载,包括静态和实例方法,总有一些适合您;这些方法都是线程安全的。

更多使用方法请参考早期关于 Swifter.Json 的文章,GitHub 或 Wiki;学习交流进 Swifter 的 QQ 群:133630914(新群,欢迎加入)。

 

Swifter 框架的特性

(1) Swifter 可以运行在 .NET Framework 2.0+, .NET Core 2.0+, .NET Standard 2.0+, MONO JIT, MONO AOT, Xamarin.Android, Xamarin.iOS, Unity JIT 等平台/运行时上,Unity IL2CPP 运行时由于没有我们测试环境,不知可否正常运行,更多信息请看下面的 AOT 说明

(2) Swifter 有着深层的抽象封装,这虽然带来了一些性能和内存的损耗,但也获得了更高的扩展性;Swifter.Json/Swifter.MessagePack/Swifter.Data 的可公用的代码非常多,这使得在 Swifter 上实现一个新的序列化库只需要编写少量代码即可实现,这是其他框架难实现的。

(3) 虽然 Swifter 有很多接口和抽象编程,但是 Swifter 并没有因此比其他的框架慢或内存占用大,反比它们更快和更小内存占用;这是因为 Swifter 从来都是使用更好算法和逻辑来获取性能,而不是使用更直接的代码获取直接的性能。

(4) 作为类库开发者,我们深知每个人开发和测试的侧重点都与他人不一样,自己找出自己的问题太难,所以 Swifter.Json 和 Swifter.MessagePack 除了我们自己的测试单元之外, 还 "偷" 了 Newtonsoft, Neuecc 和 Spanjson 的 5000+ 个测试单元( 去除了 Newtonsoft 的部分测试单元);现已测试通过 4200+ 个,不通过 800+ 个是我们认为可以允许或是更加合理的行为。(不劳而获的测试单元确实用着很爽,但事实是我们"搬"这些测试单元用了 3 天😫,无脑替换改到手指抽筋)

 

Swifter.Json 和 Swifter.MessagePack

(1) Swifter.MessagePack 和 Swifter.Json 一样,都有着非常优异的性能和极小的额外内存分配。

(2) Swifter.MessagePack 和 Swifter.Json 的 API 大致相同,如果使用者同时使用它们,那么可以极小成本在它们之间切换。

(3) 得益于 Swifter.Core 的强大数据映射,Swifter.MessagePack 和 Swifter.Json 都同时支持 .NET 上大多数常用的数据结构和类型。

(4) Swifter.MessagePack 和 Swifter.Json 对重复引用的对象的表示方式不一样,在开启 MultiReferencingReference 配置项后,Swifter.Json 将使用 { "$ref": "#/obj/1/target" } 来表示重复引用的对象,而 Swifter.MessagePack 使用对象在 MsgPack 内容的偏移量表示重复引用的对象;相比之下 Swifter.MessagePack 的方案更简单性能更快,但是可读性较差,不过说来 MsgPack 本来就是要专门的工具才能阅读😄。

(5) Swifter.MessagePack 在序列化基础类型时,在保证精度不丢失的前提下,将大数据类型转换为更小数据类型,以得到更紧凑的 MsgPack 内容(如将 double 123 转换为 int 123,int 123 只需要 1 个字节即可表示,如果不做转换则需要 9 个字节表示)。

(6) Swifter.MessagePack 在序列化未知长度的集合时(如 Enumerable<T>),会将长度定义为四字节 (FixArray32),然后在写入完成后把实际长度赋予这四字节长度;这样虽然在较短的未知长度集合时,将产生 1-3 个 0;但是这避免了将未知长度的集合转换为 List<T> 或 T[], 这提高了性能也减少了内存分配,这是不亏的(因为未知长度的集合很常用,如 Linq,DbDataReader 等)。

 

新版本做了啥?

(1) 主要是解决了已知 BUG,包括了 Issues 上提到的几个。

(2) 允许将 "" 值解析为 DateTime, int?, double 等基础类型的默认值,但是需要启用 EmptyStringAsDefault 配置项,默认未开启。

(3) 解决了 Swifter.Json 浮点数: float, double 失真的问题,并增加了 UseSystemFloatingPointsMethods 配置项使用系统的浮点数方法,此配置项的更多说明请看该配置项的注释。

(4) 增加了序列化的事件:ObjectFiltering 和 ArrayFiltering,这两个事件可以对正在序列化中的 键/值 做处理和筛选,包括驼峰命名法,忽略一些值等。它们被放在 JsonFormatter 和 MessagePackFormatter 的实例里面。

(5) 增加了 .NET 对象的持久序列化和反序列化功能,这个功能将对象序列化为包含类型信息和字段值的内容,不包含逻辑信息;使用 SerializationBox<T> 盒子使用此功能。图示:

更多新增的功能请继续看以下内容。

 

 

AOT

在 Swifter 新版本里,AOT 的 JIT 的界限更加明显,由 VersionDifferences.IsSupportEmit 字段标识;当这个字段为 true 表示当前平台是 JIT 运行时,Swifter 将在一些类中使用 Emit 技术提高性能;当此字段为 false 时,Swifter 会完全不使用 Emit 技术。

因我们设备有限,无法提供大规模的平台测试,但我们非常希望可以 Swifter 可以支持更多的平台,所以希望朋友们加 Swifter 交流 QQ 群:(133630914),在这里我们可以更快的提供反馈。

 

直接文档读取/写入的 API

通常情况下,将小型对象序列化为 Json/MsgPack 和将小型 Json/MsgPack 反序列化为对象是 .NET 程序中常见的操作,Swifter 也正以此为常用场景做优化,所以 Swifter 在对小型数据操作时性能最佳,且相比其他 Json/MsgPack 解析库优势明显。

但在大型数据下优势减少,这主要原因是大型数据的存储需要实体类或字典/集合存储,创建/填充/遍历这些对象消耗了大量资源(接口编程的损耗);所以 Swifter 提供了直接读取/写入的 API 来绕开了对存储介质的操作,以更快更小损耗的读写大型数据。

使用 JsonFormatter.CreateJsonReader/MessagePackFormatter.CreateMessagePackReader 函数来创建文档读取器,使用 JsonFormatter.CreateJsonWriter/MessagePackFormatter.CreateMessagePackWriter 函数来创建文档写入器。

使用文档读取器完整的读取一个 Json/MsgPack 文档将比反序列化为对象快 4-8 倍!使用文档写入器生成文档的性能与将实体类序列化为 Json/MsgPack 相差较小,前提是您已构建好了这些对象😄。

读取器演示:

写入器演示:

 

拥有简单预测数组的长度的能力

Swifter 在对小型数组,部分集合写入时,会根据数组的类型,来源(Data,Json,MsgPack 等),名称等信息并结合之前的一些长度记录,简单的预测出新的数组的长度;在写入完成后,如果预测长度与实际长度不符,则扩展或压缩为实际长度;如果与实际长度相符,则不需要重新创建新数组。此能力有效提高反序列化小型数组和部分集合性能,并且减少额外内存分配。

在其他高性能的 Json 解析库,它们使用 ArrayPool<T> 同样可以提高性能和减少内存分配;但是由于 Swifter 对兼容性的要求,使得我们不能使用 ArrayPool<T> 方案;在数组的长度比较稳定的情况下,我们的方案更好;但在数组长度非常不稳定的情况下,我们的方案可能仍需要 1-3 次的扩容/压缩。

 

假定有序的对象反序列化

Swifter.Json 和 Swifter.MessagePack 都支持了假定有序的对象反序列化,当一个 Json/MsgPack 的对象与当前的实体类对象的字段顺序一致时,将有效提升反序列化性能。

此操作默认不开启,可以使用 AsOrderedObjectDeserialize 配置项开启。

 

高性能的反射封装

Swifter.Core 里提供了一些对反射封装的类,它们放在 Swifter.Reflection 命名空间下;这些类型主要功能就是提高了系统反射的性能;XObjectRW 正是使用它们实现不依赖 Emit 的高性能对象读写器。

虽然放弃一些安全性检查可以提高更多的性能,但是我们并没有这么做;我们仍然有类型安全检查和防溢出检查(事实上读写字段和属性大多数的损害都在这里,如果去掉这些检查将得到上百倍的性能;事实上这些检查只起到了提示程序员不能这么做的作用,程序实际运行时这些检查无意义)。

 

高效的数字 ToString 和 Parse 方法

Swifter.Core 提供了一些高性能数字算法,包括 Int64, UInt64, Double, Single, Decimal 的 Parse 和 ToString 算法,它们被放在 Swifter.Tools.NumberHelper 里,这些算法被应用与 Swifter.Json 和一些其他地方,这些算法支持 2-64 进制。

 

XConvert 万能类型转换器

Swifter.Tools.XConvert.Convert<TSource, TDestination> 是一个功能强大的万能类型转换函数,它在初始化时尝试以下方式获取合适的转换函数:

(1) 包含在 System.Convert 里的基础转换函数;

(2) 类型兼容的隐式转换(如:从子类转换为父类,从 Int32 转换为 Int64,从 Int64 转换为 Double)。

(3) 原类型和目标类型中的 static implicit operator (隐式转换) 函数。

(4) 原类型中的 ToXXX 实例函数。

(5) 目标类型中的 Parse 和 ValueOf 静态函数。

(7) 目标类型的构造函数。

(8) 原类型和目标类型中的 static explicit operator (显式转换) 函数。

(9) 当以上方法都没有找到合适函数时,将使用 (TDestination)(object)value 进行强制转换。

简单示例:

 

性能测试

ServiceStack.Json, Jil, LitJson, NetJson 等库因为出错太多未展示出来;如果有需要,您可以到 GitHub 上自行克隆/修改/运行,已收录了 .NET 的大多数 Json 序列化库。

 

更多实用功能等你发现...

Swifter.Core 还提供了许许多多的工具类,包括反射,委托,类型转换,字符串,加密,哈希,数字,日期,数组和集合等工具,它们被放在 Swifter.Tools 命名空间下,您可以使用它们来提高开发效率和运行效率。

Swifter.RW 命名空间是整个 Swifter 框架的核心,它主要逻辑是:从读取器中读取值,写入到写入器中;如:从 JsonReader 读取值到 ObjectWriter 或 DictionaryWriter 中;熟悉它们就等于精通了 Swifter 框架。

Swifter.Json/Swifter.MessagePack 有一个非常重要的配置项 JsonFormatterOptions/MessagePackFormatterOptions;使用前建议先阅读它们,以配置更适合您系统的序列化和反序列化方案。

...

 

最后附上 Swifter.Data 的简介

Swifter.Data 是一个小型的 ORM 工具,它相比 Dapper 性能要快一些,功能要强大一些。

 

感谢阅读

posted @ 2020-06-15 07:43  陈鑫伟  阅读(1463)  评论(14编辑  收藏  举报