NET Core 3的C#JSON序列化程序之战

你好。预期“ C#开发人员”课程的开始,我们为您准备了一个有趣的翻译,我们还为您提供免费观看课程记录:“设计模式状态(状态)”










最近发布的.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渴望使用诸如的新类型Span<T>来提高性能。在不破坏功能的情况下修改像Newtonsoft这样的大型库非常困难。
  • , HTTP, UTF-8. String .NET — UTF-16. Newtonsoft UTF-8 UTF-16, . UTF-8.
  • Newtonsoft , .NET Framework ( BCL FCL), . ASP.NET Core Newtonsoft, .


在本文中,我们将运行一些基准测试,以查看新的串行器在性能方面有多好。此外,我们还将把Newtonsoft.JsonSystem.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.JsonSystem.Text.Json4倍以上这是一个惊人的差异。
  • Jil也非常快,比Newtonsoft.JsonSystem.Text.Json快约2.5倍
  • 在大多数情况下,新的System.Text.Json序列化程序的性能比Newtonsoft.Json约10%,但Dictionary除外,后者的速度慢了10%。
  • 较旧的DataContractJsonSerializer比其他版本差很多。
  • ServiceStack位于中间,表明它不再是最快的文本序列化程序。至少对于JSON。


基准测试2:序列化到流



第二组测试几乎相同,除了我们序列化为流。基准代码在这里结果:







这里可以找到更准确的数字感谢Adam Sitnik和Ahson Khan帮助我使System.Text.Json正常工作。


结果与先前的测试非常相似。Utf8JsonJil比其他人快4倍。吉尔非常快,仅次于Utf8Json在大多数情况下,DataContractJsonSerializer仍然是最慢的。在大多数情况下Newtonsoft的工作方式与System.Text.Json几乎相同,除了字典外,Newtonsoft具有明显的优势



基准测试3:从字符串反序列化



下一组测试涉及从字符串反序列化。测试代码可以在这里找到







这里可以找到更准确的数字


对于此基准测试, 我在运行DataContractJsonSerializer时遇到了一些困难,因此它不包含在结果中。否则,我们看到Jil在反序列化方面最快,而Utf8Json则排在第二位。它们比System.Text.Json快2-3倍。而且System.Text.JsonJson.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.JsonNewtonsoft.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.JsonNewtonsoftUtf8Json之间切换的方法前两个很容易。对于System.Text.Json,您根本不需要执行任何操作。要切换到Newtonsoft.Json,只需将一行添加到ConfigureServices



public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews()
    //   Newtonsoft.   -     System.Text.Json
    .AddNewtonsoftJson()
    ;


对于Utf8Json,我们需要添加自定义媒体格式化程序InputFormatterOutputFormatter并不是那么容易,但是最终我在互联网上找到了一个很好的解决方案,并且在设置中进行深入研究后,它仍然有效。还有一个带有格式化程序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远远领先于其他串行器。经过先前的测试,这并不是什么大惊喜。



在序列化方面,Utf8JsonSystem.Text.Json2,比Newtonsoft4倍。对于反序列化,Utf8JsonSystem.Text.Json快3.5倍,比Newtonsoft快6倍



给我的唯一惊喜是Newtonsoft.Json的表现多么差劲... 这可能是由于UTF-16和UTF-8问题。HTTP协议适用于UTF-8文本。Newtonsoft将此文本转换为UTF-16的.NET字符串类型。与UTF-8直接配合使用的Utf8JsonSystem.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.JsonNewtonsoft.Json是很好的支持。 Microsoft将继续在System.Text.Json上投入资源和精力,以便您可以依靠强大的支持。而JilUtf8Json去年收到很少的承诺。实际上,在过去六个月中,他们似乎没有太多维护。



一种选择是在您的应用程序中组合多个串行器。升级到更快的序列化程序以与ASP.NET集成以获得更高的性能,但是请在业务逻辑中继续使用Newtonsoft.Json以充分利用其功能集。



希望您喜欢这篇文章。祝好运)



其他基准



比较不同序列化程序的其他几个基准



当Microsoft宣布System.Text.Json时,他们展示了自己的基准,System.Text.JsonNewtonsoft.Json进行了比较。除了序列化和反序列化之外,此基准测试还对Document类进行了随机访问,Reader和Writer测试。他们还演示了“每秒查询”测试,这激发了我创建自己的查询的灵感。



.NET Core GitHub存储库包含一组类似于本文所述的基准。我非常仔细地查看了他们的测试,以确保自己没有犯错。你可以在...里找到它们微基准解决方案



Jil有自己的基准,可以比较JilNewtonsoftProtobufServiceStack



Utf8Json已在GitHub上发布了一组基准测试他们还测试二进制序列化器。



Alois Kraus对包括JSON序列化程序,二进制序列化程序和XML序列化程序在内最流行的.NET序列化程序进行了出色的深度测试它的基准测试包括.NET Core 3和.NET Framework 4.8的基准测试。






了解有关该课程的更多信息。






阅读更多:






All Articles