现有的 webapi 一般都基于 JSON 的格式来处理数据, 由于 JSON 是一个文本类的序列化协议所以在性能上自然就相对低效一些. 在. net 中常用 Newtonsoft.JSON 是最常用的组件, 由于提供简便基于完整的 JSON 的 String 方法使用起来非常方便; 但也正是这原因导致 Newtonsoft.JSON 在性能上一直被说慢, 虽然 Newtonsoft.JSON 提供 Stream 的方式来处理 JSON 不过想复用 writer 和 reader 还是需要一些应用技巧. 如果需要在网络通讯中应用 JSON, 那在这里介绍一下 SpanJson 这个组件, 并通过一些测试来讲述如何使用它.
SpanJson 介绍
SpanJson 是一个性能相对不错的 JSON 组件, 组件直接提供了 byte[] 和 stream 两种操作方式, 而这两种方式非常适合在构建自有网络通讯上使用. 通过这些基础的字节和流结构来处理可以相对降低一个大 string 的开销. 不过这个组件的热度并不高, 完善成度暂还不如 Newtonsoft.JSON, 不过 ASP.NET core 在 FrameworkBenchmarks 测试上已经引入. 可以尝试一下使用, 组件开源地址: https://github.com/Tornhoof/SpanJson
性能测试
组件提供的方法相对比较少, 从设计上来说更多是针对通讯方面的支持. 基于 Stream 的序列化可以直接挂载在 NetStream 上, 这样可以节省数据复制带来的开销. 不过反序列化不能直接在有混合数据的 Stream 上进行, 这或多或少有些可惜. 从 issues 的解答来看作者也不太愿意在混合数据流上进行调整. 接下来针对 bytes 和 Stream 使用进行一个性能测试, 而 Stream 则采用一个可复用池的设计
MemoryStream 池的设计
- public class MemoryStreamPool
- {
- private static System.Collections.Concurrent.ConcurrentStack<JsonMemoryStream> mPool = new System.Collections.Concurrent.ConcurrentStack<JsonMemoryStream>();
- public static Stream Pop()
- {
- if (!mPool.TryPop(out JsonMemoryStream result))
- {
- result = new JsonMemoryStream(1024 * 32);
- }
- return result;
- }
- public class JsonMemoryStream : MemoryStream
- {
- public JsonMemoryStream(int size) : base(size) { }
- protected override void Dispose(bool disposing)
- {
- MemoryStreamPool.Push(this);
- }
- }
- private static void Push(JsonMemoryStream stream)
- {
- stream.Position = 0;
- stream.SetLength(0);
- mPool.Push(stream);
- }
- }
测试内容
测试的方式主要针对一个简单的对象和一个对象列表, 然后在不同线程下 bytes 和 Stream pool 这两种方式的性能差别; 压测的线程数据分别是 1,2,4,8,16,24,32, 每次测试执行的总数是 100 万次, 然后统计出执行需要的时间和并发量. 测试代码:
- public class Bytes_JSON : BeetleX.Benchmark.BenchmarkBase
- {
- protected override void OnTest()
- {
- while (Increment())
- {
- var data = SpanJson.JsonSerializer.NonGeneric.Utf8.Serialize(DataHelper.Defalut.Employees[0]);
- var employees = SpanJson.JsonSerializer.Generic.Utf8.Deserialize<Employee>(data);
- }
- }
- }
- public class StreamPool_JSON : BeetleX.Benchmark.BenchmarkBase
- {
- protected override void OnTest()
- {
- RunTest();
- }
- private async void RunTest()
- {
- while (Increment())
- {
- using (Stream stream = MemoryStreamPool.Pop())
- {
- await SpanJson.JsonSerializer.NonGeneric.Utf8.SerializeAsync(DataHelper.Defalut.Employees[0], stream);
- stream.Position = 0;
- var employees = await SpanJson.JsonSerializer.Generic.Utf8.DeserializeAsync<Employee>(stream);
- }
- }
- }
- }
- public class Bytes_JSON_List : BeetleX.Benchmark.BenchmarkBase
- {
- protected override void OnTest()
- {
- while (Increment())
- {
- var data = SpanJson.JsonSerializer.NonGeneric.Utf8.Serialize(DataHelper.Defalut.Employees);
- var employees = SpanJson.JsonSerializer.Generic.Utf8.Deserialize<List<Employee>>(data);
- }
- }
- }
- public class StreamPool_JSON_List : BeetleX.Benchmark.BenchmarkBase
- {
- protected override void OnTest()
- {
- RunTest();
- }
- private async void RunTest()
- {
- while (Increment())
- {
- using (Stream stream = MemoryStreamPool.Pop())
- {
- await SpanJson.JsonSerializer.NonGeneric.Utf8.SerializeAsync(DataHelper.Defalut.Employees, stream);
- stream.Position = 0;
- var employees = await SpanJson.JsonSerializer.Generic.Utf8.DeserializeAsync<List<Employee>>(stream);
- }
- }
- }
- }
测试结果
- C:\Users\Administrator\Desktop\json_test>dotnet JsonSample.dll
- BeetleX.Benchmark [0.5.4.0] Copyright ? ikende.com 2019
- EMail:henryfan@msn.com
- GitHub:https://github.com/ikende
- -------------------------------------------------------------------------------
- |Name | Round| Threads| Count| Use time(s)| Sec|
- -------------------------------------------------------------------------------
- |Bytes_JSON | 1| 1| 1000000| 5.57|179580|
- -------------------------------------------------------------------------------
- |StreamPool_JSON | 1| 1| 1000000| 5.44|183898|
- -------------------------------------------------------------------------------
- |Bytes_JSON_List | 1| 1| 1000000| 43.01| 23248|
- -------------------------------------------------------------------------------
- |StreamPool_JSON_List | 1| 1| 1000000| 42.75| 23391|
- -------------------------------------------------------------------------------
- |Bytes_JSON | 1| 2| 1000000| 2.81|355990|
- -------------------------------------------------------------------------------
- |StreamPool_JSON | 1| 2| 1000000| 2.95|338969|
- -------------------------------------------------------------------------------
- |Bytes_JSON_List | 1| 2| 1000000| 23.16| 43180|
- -------------------------------------------------------------------------------
- |StreamPool_JSON_List | 1| 2| 1000000| 22.4| 44650|
- -------------------------------------------------------------------------------
- |Bytes_JSON | 1| 4| 1000000| 1.51|661246|
- -------------------------------------------------------------------------------
- |StreamPool_JSON | 1| 4| 1000000| 1.57|636130|
- -------------------------------------------------------------------------------
- |Bytes_JSON_List | 1| 4| 1000000| 13.35| 74915|
- -------------------------------------------------------------------------------
- |StreamPool_JSON_List | 1| 4| 1000000| 11.97| 83508|
- -------------------------------------------------------------------------------
- |Bytes_JSON | 1| 8| 1000000| .83|1199453|
- --------------------------------------------------------------------------------
- |StreamPool_JSON | 1| 8| 1000000| .88|1142495|
- --------------------------------------------------------------------------------
- |Bytes_JSON_List | 1| 8| 1000000| 9.24|108228|
- -------------------------------------------------------------------------------
- |StreamPool_JSON_List | 1| 8| 1000000| 6.75|148132|
- -------------------------------------------------------------------------------
- |Bytes_JSON | 1| 16| 1000000| .56|1795910|
- --------------------------------------------------------------------------------
- |StreamPool_JSON | 1| 16| 1000000| .74|1344851|
- --------------------------------------------------------------------------------
- |Bytes_JSON_List | 1| 16| 1000000| 7.67|130424|
- -------------------------------------------------------------------------------
- |StreamPool_JSON_List | 1| 16| 1000000| 4.61|216860|
- -------------------------------------------------------------------------------
- |Bytes_JSON | 1| 24| 1000000| .54|1849769|
- --------------------------------------------------------------------------------
- |StreamPool_JSON | 1| 24| 1000000| .73|1361382|
- --------------------------------------------------------------------------------
- |Bytes_JSON_List | 1| 24| 1000000| 7.61|131373|
- -------------------------------------------------------------------------------
- |StreamPool_JSON_List | 1| 24| 1000000| 4.7|212779|
- -------------------------------------------------------------------------------
- |Bytes_JSON | 1| 32| 1000000| .55|1825484|
- --------------------------------------------------------------------------------
- |StreamPool_JSON | 1| 32| 1000000| .75|1339050|
- --------------------------------------------------------------------------------
- |Bytes_JSON_List | 1| 32| 1000000| 8.01|124885|
- -------------------------------------------------------------------------------
- |StreamPool_JSON_List | 1| 32| 1000000| 5.21|192038|
- -------------------------------------------------------------------------------
- Test completed!
总结
从测试结果来看, 如果序列化的对象比小, 那可以直接基于 bytes 的方式. 虽然会产生新的 bytes 对象, 不过由于对象比较小, 引起的分配和回收并没有对象池操作上的损耗高. 不过如果对象相对复杂些的情况下, 那对象池的作用就能发挥出来, 并发越大其作用越明显!, 当并发线程数达到 8 的时候, 效率已经明显抛开! 由于业务上的数据信息都相对比较复杂些, 所以在处理上还是建议通过对象池的方式来完成 JSON 序列化处理.
下载测试代码
http://ikende.com/Files/JsonSample.zip
来源: https://www.cnblogs.com/smark/p/10689028.html