最近发布的.NET Core 3带来了许多创新。除了C#8和对WinForms和WPF的支持之外,最新版本还添加了一个新的JSON(de)序列化程序-System.Text.Json,顾名思义,其所有类都在此命名空间中。
这是一项重大创新。JSON序列化是Web应用程序中的重要因素。当今的大多数REST API都依赖于此。当您的JavaScript客户端在POST请求的正文中发送JSON时,服务器将使用JSON反序列化将其转换为C#对象。并且当服务器返回一个对象作为响应时,它将该对象序列化为JSON,以便您的JavaScript客户端可以理解它。这些是对带有对象的每个请求执行的大型操作。它们的性能会显着影响应用程序的性能,这是我现在要演示的。
如果您有使用.NET的经验,那么您一定已经听说过出色的Json.NET序列化程序,也称为Newtonsoft.Json。那么,当我们已经有了漂亮的Newtonsoft.Json时,为什么还需要一个新的序列化程序?虽然Newtonsoft.Json无疑很棒,但是有一些很好的理由来替换它:
- Microsoft渴望使用诸如的新类型
来提高性能。在不破坏功能的情况下修改像Newtonsoft这样的大型库非常困难。Span<
T> - , HTTP, UTF-8.
String
.NET — UTF-16. Newtonsoft UTF-8 UTF-16, . UTF-8. - Newtonsoft , .NET Framework ( BCL FCL), . ASP.NET Core Newtonsoft, .
在本文中,我们将运行一些基准测试,以查看新的串行器在性能方面有多好。此外,我们还将把Newtonsoft.Json和System.Text.Json与其他知名的序列化程序进行比较,并了解它们之间的关系。
序列化之战
这是我们的尺子:
- Newtonsoft.Json(也称为Json.NET)是当前的串行器行业标准。尽管它是第三方,但已集成到ASP.NET中。NuGet软件包始终为1号。一个屡获殊荣的图书馆(可能不确定)。
- System.Text.Json — Microsoft. Newtonsoft.Json. ASP.NET Core 3. .NET, NuGet ( ).
- DataContractJsonSerializer — , Microsoft, ASP.NET , Newtonsoft.Json.
- Jil — JSON Sigil
- ServiceStack — .NET JSON, JSV CSV. .NET ( ).
- Utf8Jso n是另一个自称为C#最快的JSON序列化程序。使用零内存分配工作,并直接读取/写入UTF8二进制文件,以获得更好的性能。
请注意,那里有非JSON序列化器,速度更快。特别是,protobuf-net是一个二进制序列化程序,它应该比本文中任何比较的序列化程序都要快(尽管尚未经过基准测试)。
基准结构
串行器不容易比较。我们将需要比较序列化和反序列化。我们将需要比较不同类型的类(小型和大型),列表和字典。而且,我们需要比较不同的序列化目标:字符串,流和字符数组(UTF-8数组)。这是一个相当大的测试矩阵,但我会尽力使它尽可能有条理和简洁。
我们将测试4种不同的功能:
- 序列化为字符串
- 序列化到流
- 从字符串反序列化
- 每秒在ASP.NET Core 3应用上的请求
对于每种对象,我们将测试不同类型的对象(您可以在GitHub上看到):
- 具有3个基本类型属性的小类。
- 具有大约25个属性的大型类,DateTime和几个枚举
- 1000个元素列表(小类)
- 1000个元素的字典(小类)
这些并不是全部必要的基准,但我认为,它们足以使人们有一个普遍的想法。
对于所有的基准测试我用BenchmarkDotNet以下系统:BenchmarkDotNet=v0.11.5, OS=Windows 10.0.17134.1069 (1803/April2018Update/Redstone4) Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores. .NET Core SDK=3.0.100. Host : .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), 64bit RyuJIT
。您可以在GitHub上找到基准测试项目本身。
所有测试仅在.NET Core 3项目上运行。
基准测试1:序列化为字符串
我们要检查的第一件事是将对象样本序列化为字符串。
基准代码本身非常简单(请参阅GitHub上的代码):
public class SerializeToString<T> where T : new()
{
private T _instance;
private DataContractJsonSerializer _dataContractJsonSerializer;
[GlobalSetup]
public void Setup()
{
_instance = new T();
_dataContractJsonSerializer = new DataContractJsonSerializer(typeof(T));
}
[Benchmark]
public string RunSystemTextJson()
{
return JsonSerializer.Serialize(_instance);
}
[Benchmark]
public string RunNewtonsoft()
{
return JsonConvert.SerializeObject(_instance);
}
[Benchmark]
public string RunDataContractJsonSerializer()
{
using (MemoryStream stream1 = new MemoryStream())
{
_dataContractJsonSerializer.WriteObject(stream1, _instance);
stream1.Position = 0;
using var sr = new StreamReader(stream1);
return sr.ReadToEnd();
}
}
[Benchmark]
public string RunJil()
{
return Jil.JSON.Serialize(_instance);
}
[Benchmark]
public string RunUtf8Json()
{
return Utf8Json.JsonSerializer.ToJsonString(_instance);
}
[Benchmark]
public string RunServiceStack()
{
return SST.JsonSerializer.SerializeToString(_instance);
}
}
上面的测试类是通用的,因此我们可以使用相同的代码测试所有对象,例如:
BenchmarkRunner.Run<SerializeToString<Models.BigClass>>();
在使用所有序列化程序运行所有测试类之后,我们得到以下结果:
可以在这里找到更准确的指标
- Utf8Json是迄今为止最快的,比Newtonsoft.Json和System.Text.Json快4倍以上。这是一个惊人的差异。
- Jil也非常快,比Newtonsoft.Json和System.Text.Json快约2.5倍。
- 在大多数情况下,新的System.Text.Json序列化程序的性能比Newtonsoft.Json好约10%,但Dictionary除外,后者的速度慢了10%。
- 较旧的DataContractJsonSerializer比其他版本差很多。
- ServiceStack位于中间,表明它不再是最快的文本序列化程序。至少对于JSON。
基准测试2:序列化到流
第二组测试几乎相同,除了我们序列化为流。基准代码在这里。结果:
在这里可以找到更准确的数字。感谢Adam Sitnik和Ahson Khan帮助我使System.Text.Json正常工作。
结果与先前的测试非常相似。Utf8Json和Jil比其他人快4倍。吉尔非常快,仅次于Utf8Json。在大多数情况下,DataContractJsonSerializer仍然是最慢的。在大多数情况下,Newtonsoft的工作方式与System.Text.Json几乎相同,除了字典外,Newtonsoft具有明显的优势。
基准测试3:从字符串反序列化
下一组测试涉及从字符串反序列化。测试代码可以在这里找到。
在这里可以找到更准确的数字。
对于此基准测试, 我在运行DataContractJsonSerializer时遇到了一些困难,因此它不包含在结果中。否则,我们看到Jil在反序列化方面是最快的,而Utf8Json则排在第二位。它们比System.Text.Json快2-3倍。而且System.Text.Json比Json.NET快30%。
到目前为止,事实证明,流行的Newtonsoft.Json和新的System.Text.Json的性能明显比竞争对手差。由于Newtonsoft的流行,这对我来说是一个相当意外的结果。以及围绕新的最佳表现Microsoft System.Text.Json的所有宣传。让我们在ASP.NET应用程序中进行检查。
基准4:.NET服务器上每秒的请求数
如前所述,JSON序列化非常重要,因为它在REST API中一直存在。使用内容类型对服务器的HTTP请求
application/json
将需要序列化或反序列化JSON对象。当服务器在POST请求中接受有效负载时,服务器将从JSON反序列化。当服务器在响应中返回对象时,它会序列化JSON。现代的客户端-服务器通信严重依赖JSON序列化。因此,要测试“真实”场景,创建测试服务器并衡量其性能是有意义的。
我受到Microsoft性能测试的启发他们创建了一个MVC服务器应用程序,并每秒检查一次请求。Microsoft基准测试System.Text.Json和Newtonsoft.Json。在本文中,我们将做同样的事情,只是我们将它们与Utf8Json进行比较,后者已被证明是先前测试中最快的串行器之一。
不幸的是,我无法将ASP.NET Core 3与Jil集成在一起,因此基准测试不包括它。我很确定可以通过更多的努力来完成,但是but。
事实证明,创建此测试比以前更加困难。我首先创建了一个ASP.NET Core 3.0 MVC应用程序,就像在Microsoft基准测试中一样。我添加了一个用于性能测试的控制器,类似于Microsoft测试中的控制器:
[Route("mvc")]
public class JsonSerializeController : Controller
{
private static Benchmarks.Serializers.Models.ThousandSmallClassList _thousandSmallClassList
= new Benchmarks.Serializers.Models.ThousandSmallClassList();
[HttpPost("DeserializeThousandSmallClassList")]
[Consumes("application/json")]
public ActionResult DeserializeThousandSmallClassList([FromBody]Benchmarks.Serializers.Models.ThousandSmallClassList obj) => Ok();
[HttpGet("SerializeThousandSmallClassList")]
[Produces("application/json")]
public object SerializeThousandSmallClassList() => _thousandSmallClassList;
}
当客户端调用端点时
DeserializeThousandSmallClassList
,服务器将接受JSON文本并反序列化内容。这就是我们测试反序列化的方式。客户端调用时SerializeThousandSmallClassList
,服务器将返回1000个SmallClass
项目的列表,从而将内容序列化为JSON。
然后,我们需要取消每个请求的日志记录,以免影响结果:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureLogging(logging =>
{
logging.ClearProviders();
//logging.AddConsole();
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
现在,我们需要一种在System.Text.Json,Newtonsoft和Utf8Json之间切换的方法。前两个很容易。对于System.Text.Json,您根本不需要执行任何操作。要切换到Newtonsoft.Json,只需将一行添加到
ConfigureServices
:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews()
// Newtonsoft. - System.Text.Json
.AddNewtonsoftJson()
;
对于Utf8Json,我们需要添加自定义媒体格式化程序
InputFormatter
和OutputFormatter
。并不是那么容易,但是最终我在互联网上找到了一个很好的解决方案,并且在设置中进行深入研究后,它仍然有效。还有一个带有格式化程序的NuGet程序包,但不适用于ASP.NET Core 3。
internal sealed class Utf8JsonInputFormatter : IInputFormatter
{
private readonly IJsonFormatterResolver _resolver;
public Utf8JsonInputFormatter1() : this(null) { }
public Utf8JsonInputFormatter1(IJsonFormatterResolver resolver)
{
_resolver = resolver ?? JsonSerializer.DefaultResolver;
}
public bool CanRead(InputFormatterContext context) => context.HttpContext.Request.ContentType.StartsWith("application/json");
public async Task<InputFormatterResult> ReadAsync(InputFormatterContext context)
{
var request = context.HttpContext.Request;
if (request.Body.CanSeek && request.Body.Length == 0)
return await InputFormatterResult.NoValueAsync();
var result = await JsonSerializer.NonGeneric.DeserializeAsync(context.ModelType, request.Body, _resolver);
return await InputFormatterResult.SuccessAsync(result);
}
}
internal sealed class Utf8JsonOutputFormatter : IOutputFormatter
{
private readonly IJsonFormatterResolver _resolver;
public Utf8JsonOutputFormatter1() : this(null) { }
public Utf8JsonOutputFormatter1(IJsonFormatterResolver resolver)
{
_resolver = resolver ?? JsonSerializer.DefaultResolver;
}
public bool CanWriteResult(OutputFormatterCanWriteContext context) => true;
public async Task WriteAsync(OutputFormatterWriteContext context)
{
if (!context.ContentTypeIsServerDefined)
context.HttpContext.Response.ContentType = "application/json";
if (context.ObjectType == typeof(object))
{
await JsonSerializer.NonGeneric.SerializeAsync(context.HttpContext.Response.Body, context.Object, _resolver);
}
else
{
await JsonSerializer.NonGeneric.SerializeAsync(context.ObjectType, context.HttpContext.Response.Body, context.Object, _resolver);
}
}
}
现在让ASP.NET使用以下格式化程序:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews()
// Newtonsoft
//.AddNewtonsoftJson()
// Utf8Json
.AddMvcOptions(option =>
{
option.OutputFormatters.Clear();
option.OutputFormatters.Add(new Utf8JsonOutputFormatter1(StandardResolver.Default));
option.InputFormatters.Clear();
option.InputFormatters.Add(new Utf8JsonInputFormatter1());
});
}
这就是服务器。现在介绍客户。
C#客户端每秒测量请求
我还创建了一个C#客户端应用程序,尽管JavaScript客户端在大多数实际场景中都占优势。对我们而言,这无关紧要。这是代码:
public class RequestPerSecondClient
{
private const string HttpsLocalhost = "https://localhost:5001/";
public async Task Run(bool serialize, bool isUtf8Json)
{
await Task.Delay(TimeSpan.FromSeconds(5));
var client = new HttpClient();
var json = JsonConvert.SerializeObject(new Models.ThousandSmallClassList());
// ,
for (int i = 0; i < 100; i++)
{
await DoRequest(json, client, serialize);
}
int count = 0;
Stopwatch sw = new Stopwatch();
sw.Start();
while (sw.Elapsed < TimeSpan.FromSeconds(1))
{
count++;
await DoRequest(json, client, serialize);
}
Console.WriteLine("Requests in one second: " + count);
}
private async Task DoRequest(string json, HttpClient client, bool serialize)
{
if (serialize)
await DoSerializeRequest(client);
else
await DoDeserializeRequest(json, client);
}
private async Task DoDeserializeRequest(string json, HttpClient client)
{
var uri = new Uri(HttpsLocalhost + "mvc/DeserializeThousandSmallClassList");
var content = new StringContent(json, Encoding.UTF8, "application/json");
var result = await client.PostAsync(uri, content);
result.Dispose();
}
private async Task DoSerializeRequest(HttpClient client)
{
var uri = HttpsLocalhost + "mvc/SerializeThousandSmallClassList";
var result = await client.GetAsync(uri);
result.Dispose();
}
}
该客户端将持续发送请求1秒钟,并对其进行计数。
结果
因此,事不宜迟,这里是结果:
可以在这里找到更准确的指标
Utf8Json远远领先于其他串行器。经过先前的测试,这并不是什么大惊喜。
在序列化方面,Utf8Json比System.Text.Json快2倍,比Newtonsoft快4倍。对于反序列化,Utf8Json比System.Text.Json快3.5倍,比Newtonsoft快6倍。
给我的唯一惊喜是Newtonsoft.Json的表现多么差劲... 这可能是由于UTF-16和UTF-8问题。HTTP协议适用于UTF-8文本。Newtonsoft将此文本转换为UTF-16的.NET字符串类型。与UTF-8直接配合使用的Utf8Json或System.Text.Json都不存在这种开销。
重要的是要注意,这些基准不应被100%信任,因为它们可能无法完全反映实际情况。这就是为什么:
- 我在本地计算机上运行了所有内容-客户端和服务器。在实际情况下,服务器和客户端位于不同的计算机上。
- . , . . - . , , . , , GC. Utf8Json, .
- Microsoft ( 100 000). , , , , .
- . , - - .
考虑所有因素,这些结果令人难以置信。通过选择正确的JSON序列化程序,似乎可以大大缩短响应时间。从Newtonsoft切换到System.Text.Json可以将请求数量增加2-7倍,从Newtonsoft切换到Utf8Json可以提高6-14倍。这是不完全公平的,因为真正的服务器将做的不仅仅是接受参数和返回对象。它还可能还会做其他事情,例如使用数据库并因此执行一些业务逻辑,因此序列化时间可能不太重要。但是,这些数字令人难以置信。
结论
让我们总结一下:
- System.Text.Json , Newtonsoft.Json ( ). Microsoft .
- , Newtonsoft.Json System.Text.Json. , Utf8Json Jil 2-4 , System.Text.Json.
- , Utf8Json ASP.NET . , , , ASP.NET.
这是否意味着我们都应该切换到Utf8Json或Jil?答案可能是……也许。请记住,Newtonsoft.Json经受了时间的考验,成为有原因的最受欢迎的串行器。它支持许多功能,已经通过各种类型的边缘保护盒进行了测试,并且具有大量记录在案的解决方案和解决方法。无论System.Text.Json和Newtonsoft.Json是很好的支持。 Microsoft将继续在System.Text.Json上投入资源和精力,以便您可以依靠强大的支持。而Jil和Utf8Json去年收到很少的承诺。实际上,在过去六个月中,他们似乎没有太多维护。
一种选择是在您的应用程序中组合多个串行器。升级到更快的序列化程序以与ASP.NET集成以获得更高的性能,但是请在业务逻辑中继续使用Newtonsoft.Json以充分利用其功能集。
希望您喜欢这篇文章。祝好运)
其他基准
比较不同序列化程序的其他几个基准
当Microsoft宣布System.Text.Json时,他们展示了自己的基准,将System.Text.Json和Newtonsoft.Json进行了比较。除了序列化和反序列化之外,此基准测试还对Document类进行了随机访问,Reader和Writer测试。他们还演示了“每秒查询”测试,这激发了我创建自己的查询的灵感。
.NET Core GitHub存储库包含一组类似于本文所述的基准。我非常仔细地查看了他们的测试,以确保自己没有犯错。你可以在...里找到它们微基准解决方案。
Jil有自己的基准,可以比较Jil,Newtonsoft,Protobuf和ServiceStack。
Utf8Json已在GitHub上发布了一组基准测试。他们还测试二进制序列化器。
Alois Kraus对包括JSON序列化程序,二进制序列化程序和XML序列化程序在内的最流行的.NET序列化程序进行了出色的深度测试。它的基准测试包括.NET Core 3和.NET Framework 4.8的基准测试。
了解有关该课程的更多信息。