Der kürzlich veröffentlichte .NET Core 3 bringt eine Reihe von Innovationen mit sich. Zusätzlich zu C # 8 und der Unterstützung von WinForms und WPF wurde in der neuesten Version ein neuer JSON (de) serializer - System.Text.Json - hinzugefügt . Wie der Name schon sagt, befinden sich alle Klassen in diesem Namespace.
Dies ist eine wichtige Innovation. Die JSON-Serialisierung ist ein wichtiger Faktor in Webanwendungen.Der größte Teil der heutigen REST-API basiert darauf. Wenn Ihr Javascript-Client JSON im Hauptteil einer POST-Anforderung sendet, verwendet der Server die JSON-Deserialisierung, um es in ein C # -Objekt zu konvertieren. Wenn der Server als Antwort ein Objekt zurückgibt, serialisiert er dieses Objekt an JSON, damit Ihr Javascript-Client es verstehen kann. Dies sind große Operationen, die bei jeder Anforderung mit Objekten ausgeführt werden. Ihre Leistung kann die Leistung von Anwendungen erheblich beeinträchtigen, was ich jetzt demonstrieren werde.
Wenn Sie Erfahrung mit .NET haben, müssen Sie von dem hervorragenden Json.NET- Serializer gehört haben , der auch als Newtonsoft.Json bekannt ist . Also warum brauchen wir einen neuen Serializer , wenn wir schon die schöne Newtonsoft.Json haben ? Während Newtonsoft.Json zweifellos großartig ist, gibt es einige gute Gründe, es zu ersetzen:
- Microsoft war daran interessiert, neue Typen zu verwenden, um beispielsweise
die Leistung zu verbessern. Es ist sehr schwierig, eine große Bibliothek wie Newtonsoft zu ändern, ohne die Funktionalität zu beeinträchtigen.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, .
In diesem Artikel werden einige Benchmarks durchgeführt, um festzustellen, wie viel besser der neue Serializer in Bezug auf die Leistung ist. Darüber hinaus werden wir Newtonsoft.Json und System.Text.Json mit anderen bekannten Serialisierern vergleichen und sehen, wie sie im Verhältnis zueinander umgehen.
Schlacht der Serialisierer
Hier ist unser Herrscher:
- Newtonsoft.Json (auch bekannt als Json.NET ) ist ein Serializer, der derzeit der Industriestandard ist. Es wurde in ASP.NET integriert, obwohl es von Drittanbietern stammt. NuGet-Paket Nr. 1 aller Zeiten. Eine mehrfach preisgekrönte Bibliothek (wahrscheinlich nicht sicher).
- 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 ist ein weiterer selbsternannter schnellster C # to JSON-Serializer. Funktioniert mit einer Speicherzuweisung von Null und liest / schreibt direkt in die UTF8-Binärdatei, um eine bessere Leistung zu erzielen.

Beachten Sie, dass es Nicht-JSON-Serialisierer gibt, die schneller sind. Insbesondere ist protobuf-net ein binärer Serializer, der schneller sein sollte als jeder der in diesem Artikel verglichenen Serializer (der jedoch nicht durch Benchmarks getestet wurde).
Benchmark-Struktur
Serializer sind nicht einfach zu vergleichen. Wir müssen Serialisierung und Deserialisierung vergleichen. Wir müssen verschiedene Arten von Klassen (klein und groß), Listen und Wörterbücher vergleichen. Und wir müssen verschiedene Serialisierungsziele vergleichen: Zeichenfolgen, Streams und Zeichenarrays (UTF-8-Arrays). Dies ist eine ziemlich große Matrix von Tests, aber ich werde versuchen, sie so organisiert und präzise wie möglich zu halten.
Wir werden 4 verschiedene Funktionen testen:
- Serialisierung zum String
- Serialisierung zu einem Stream
- Deserialisieren von Zeichenfolge
- Anforderungen pro Sekunde in der ASP.NET Core 3-App
Für jedes werden wir verschiedene Arten von Objekten testen (die Sie auf GitHub sehen können ):
- Kleine Klasse mit nur 3 primitiven Typeneigenschaften.
- Große Klasse mit ca. 25 Eigenschaften, DateTime und ein paar Aufzählungen
- Liste von 1000 Elementen (kleine Klasse)
- Wörterbuch mit 1000 Elementen (kleine Klasse)
Dies sind nicht alle notwendigen Benchmarks, aber meiner Meinung nach reichen sie aus, um sich einen Überblick zu verschaffen.
Für alle Benchmarks habe ich BenchmarkDotNet auf folgendem System verwendet : 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. Sie finden das Benchmark-Projekt selbst auf GitHub .
Alle Tests werden nur in .NET Core 3-Projekten ausgeführt.
Benchmark 1: Serialisierung zu String
Das erste, was wir überprüfen werden, ist die Serialisierung unserer Objektprobe in eine Zeichenfolge.
Der Benchmark-Code selbst ist ziemlich einfach (siehe auf 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);
}
}
Die obige Testklasse ist generisch, sodass wir alle unsere Objekte mit demselben Code testen können, zum Beispiel:
BenchmarkRunner.Run<SerializeToString<Models.BigClass>>();
Nachdem wir alle Testklassen mit allen Serialisierern ausgeführt haben, haben wir die folgenden Ergebnisse erhalten:
Genauere Indikatoren finden Sie hier
- Utf8Json ist das bisher schnellste, mehr als viermal schneller als Newtonsoft.Json und System.Text.Json . Dies ist ein bemerkenswerter Unterschied.
- Jil ist auch sehr schnell, ungefähr 2,5x schneller als Newtonsoft.Json und System.Text.Json.
- In den meisten Fällen ist der neue System.Text.Json- Serializer um etwa 10% besser als Newtonsoft.Json , mit Ausnahme von Dictionary, wo er 10% langsamer war.
- Der ältere DataContractJsonSerializer ist viel schlechter als alle anderen.
- ServiceStack befindet sich genau in der Mitte und zeigt, dass es nicht mehr der schnellste Textserialisierer ist. Zumindest für JSON.
Benchmark 2: Serialisierung zum Streamen
Die zweite Reihe von Tests ist ziemlich gleich, außer dass wir in einen Stream serialisieren. Der Benchmark-Code ist hier . Ergebnisse:
Genauere Zahlen finden Sie hier . Vielen Dank an Adam Sitnik und Ahson Khan, die mir geholfen haben, System.Text.Json zum Laufen zu bringen.
Die Ergebnisse sind dem vorherigen Test sehr ähnlich. Utf8Json und Jil sind viermal schneller als andere. Jil ist sehr schnell, nach Utf8Json an zweiter Stelle . Der DataContractJsonSerializer ist in den meisten Fällen immer noch der langsamste. Newtonsoft funktioniert in den meisten Fällen ähnlich wie System.Text.Json , mit Ausnahme von Wörterbüchern, in denen Newtonsoft einen spürbaren Vorteil hat .
Benchmark 3: Deserialisieren von String
Die nächste Reihe von Tests befasst sich mit der Deserialisierung aus einem String. Den Testcode finden Sie hier .
Genauere Zahlen finden Sie hier .
Ich habe einige Schwierigkeiten, den DataContractJsonSerializer für diesen Benchmark auszuführen, daher ist er nicht in den Ergebnissen enthalten. Ansonsten sehen wir, dass Jil bei der Deserialisierung am schnellsten ist, Utf8Json liegt an zweiter Stelle. Sie sind 2-3 mal schneller als System.Text.Json . Und System.Text.Json ist ungefähr 30% schneller als Json.NET .
Bisher hat sich herausgestellt, dass das beliebte Newtonsoft.Json und das neue System.Text.Json eine deutlich schlechtere Leistung aufweisen als ihre Konkurrenten. Dies war aufgrund der Beliebtheit von Newtonsoft.Json ein ziemlich unerwartetes Ergebnis für michund der ganze Hype um den neuen Top-Performer Microsoft System.Text.Json . Lassen Sie es uns in einer ASP.NET-Anwendung überprüfen.
Benchmark 4: Die Anzahl der Anforderungen pro Sekunde auf dem .NET-Server
Wie bereits erwähnt, ist die JSON-Serialisierung sehr wichtig, da sie in REST-APIs ständig vorhanden ist. HTTP-Anforderungen an einen Server, der den Inhaltstyp verwendet
application/json, müssen das JSON-Objekt serialisieren oder deserialisieren . Wenn der Server die Nutzdaten in einer POST-Anforderung akzeptiert, deserialisiert der Server von JSON. Wenn der Server in seiner Antwort ein Objekt zurückgibt, serialisiert er JSON. Die moderne Client-Server-Kommunikation basiert stark auf der JSON-Serialisierung. Um das "echte" Szenario zu testen, ist es daher sinnvoll, einen Testserver zu erstellen und dessen Leistung zu messen. Der Microsoft-Leistungstest hat
mich inspiriertin dem sie eine MVC-Serveranwendung erstellt und die Anforderungen pro Sekunde überprüft haben. Microsoft-Benchmarks testen System.Text.Json und Newtonsoft.Json . In diesem Artikel werden wir dasselbe tun, außer dass wir sie mit Utf8Json vergleichen werden , das sich in früheren Tests als einer der schnellsten Serialisierer erwiesen hat.
Leider konnte ich ASP.NET Core 3 nicht in Jil integrieren, daher enthält der Benchmark dies nicht. Ich bin mir ziemlich sicher, dass dies mit mehr Aufwand möglich ist, aber leider.
Die Erstellung dieses Tests erwies sich als schwieriger als zuvor. Ich habe zuerst eine ASP.NET Core 3.0 MVC-App erstellt, genau wie im Microsoft-Benchmark. Ich habe einen Controller für Leistungstests hinzugefügt, ähnlich dem im Microsoft-Test :
[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;
}
Wenn der Client den Endpunkt aufruft
DeserializeThousandSmallClassList, akzeptiert der Server den JSON-Text und deserialisiert den Inhalt. So testen wir die Deserialisierung. Wenn der Client aufruft SerializeThousandSmallClassList, gibt der Server eine Liste mit 1000 SmallClassElementen zurück und serialisiert dadurch den Inhalt an JSON.
Dann müssen wir die Protokollierung für jede Anforderung abbrechen, damit das Ergebnis nicht beeinflusst wird:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureLogging(logging =>
{
logging.ClearProviders();
//logging.AddConsole();
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
Wir brauchen jetzt eine Möglichkeit, zwischen System.Text.Json , Newtonsoft und Utf8Json zu wechseln . Mit den ersten beiden ist es einfach. Für System.Text.Json müssen Sie überhaupt nichts tun. Um zu Newtonsoft.Json zu wechseln, fügen Sie einfach eine Zeile hinzu zu
ConfigureServices :
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews()
// Newtonsoft. - System.Text.Json
.AddNewtonsoftJson()
;
Für Utf8Json müssen wir benutzerdefinierte Medienformatierer
InputFormatterund hinzufügen OutputFormatter. Es war nicht so einfach, aber am Ende fand ich eine gute Lösung im Internet , und nachdem ich mich in den Einstellungen umgesehen hatte, funktionierte es. Es gibt auch ein NuGet-Paket mit Formatierern, das jedoch nicht mit ASP.NET Core 3 funktioniert.
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);
}
}
}
Damit ASP.NET nun diesen Formatierer verwenden kann:
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());
});
}
Das ist also der Server. Nun zum Kunden.
C # -Client zum Messen von Anforderungen pro Sekunde
Ich habe auch eine C # -Client-Anwendung erstellt, obwohl JavaScript-Clients in den meisten realen Szenarien vorherrschen. Für unsere Zwecke spielt es keine Rolle. Hier ist der Code:
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();
}
}
Dieser Client sendet kontinuierlich Anforderungen für 1 Sekunde und zählt sie.
Ergebnisse
Also, ohne weiteres, hier sind die Ergebnisse:
Genauere Indikatoren finden Sie hier
Utf8Json übertraf andere Serializer um ein Vielfaches . Dies war nach früheren Tests keine große Überraschung.
In Bezug auf die Serialisierung ist Utf8Json 2x schneller als System.Text.Json und 4x schneller als Newtonsoft . Für die Deserialisierung ist Utf8Json 3,5-mal schneller als System.Text.Json und 6-mal schneller als Newtonsoft .
Die einzige Überraschung für mich ist, wie schlecht Newtonsoft.Json funktioniert... Dies ist wahrscheinlich auf das Problem mit UTF-16 und UTF-8 zurückzuführen. Das HTTP-Protokoll arbeitet mit UTF-8-Text. Newtonsoft konvertiert diesen Text in .NET-Zeichenfolgentypen, die UTF-16 sind. Dieser Overhead ist weder in Utf8Json noch in System.Text.Json vorhanden , die direkt mit UTF-8 arbeiten.
Es ist wichtig zu beachten, dass diese Benchmarks nicht zu 100% vertrauenswürdig sein sollten, da sie möglicherweise das tatsächliche Szenario nicht vollständig widerspiegeln. Und deshalb:
- Ich habe alles auf meinem lokalen Computer ausgeführt - sowohl Client als auch Server. In einem realen Szenario befinden sich Server und Client auf verschiedenen Computern.
- . , . . - . , , . , , GC. Utf8Json, .
- Microsoft ( 100 000). , , , , .
- . , - - .
Alles in allem sind diese Ergebnisse ziemlich unglaublich. Es scheint, dass die Antwortzeit durch Auswahl des richtigen JSON-Serialisierers erheblich verbessert werden kann. Durch den Wechsel von Newtonsoft zu System.Text.Json wird die Anzahl der Anforderungen um das 2-7-fache und durch den Wechsel von Newtonsoft zu Utf8Json um das 6-14-fache verbessert. Dies ist nicht ganz fair, da ein echter Server viel mehr kann als nur Argumente zu akzeptieren und Objekte zurückzugeben. Es wird wahrscheinlich auch andere Dinge tun, wie die Arbeit mit Datenbanken und damit die Ausführung einer Geschäftslogik, sodass die Serialisierungszeit möglicherweise weniger wichtig ist. Diese Zahlen sind jedoch unglaublich.
Schlussfolgerungen
Fassen wir zusammen:
- System.Text.Json , Newtonsoft.Json ( ). Microsoft .
- , Newtonsoft.Json System.Text.Json. , Utf8Json Jil 2-4 , System.Text.Json.
- , Utf8Json ASP.NET . , , , ASP.NET.
Bedeutet dies, dass wir alle zu Utf8Json oder Jil wechseln sollten? Die Antwort darauf ist ... vielleicht. Denken Sie daran, Newtonsoft.Json hat den Test der Zeit bestanden und ist aus einem bestimmten Grund zum beliebtesten Serializer geworden. Es unterstützt viele Funktionen, wurde mit allen Arten von Randfällen getestet und verfügt über unzählige dokumentierte Lösungen und Problemumgehungen. Sowohl System.Text.Json als auch Newtonsoft.Json werden sehr gut unterstützt. Microsoft wird weiterhin Ressourcen und Anstrengungen in System.Text.Json investieren, damit Sie auf eine hervorragende Unterstützung zählen können. Während Jil und Utf8Jsonhat im letzten Jahr nur sehr wenige Zusagen erhalten. Tatsächlich sieht es so aus, als hätten sie in den letzten 6 Monaten nicht viel gewartet.
Eine Möglichkeit besteht darin, mehrere Serializer in Ihrer Anwendung zu kombinieren. Aktualisieren Sie auf schnellere Serializer für die ASP.NET-Integration, um eine überlegene Leistung zu erzielen. Verwenden Sie jedoch weiterhin Newtonsoft.Json in Ihrer Geschäftslogik, um das Beste aus den Funktionen herauszuholen.
Ich hoffe, Ihnen hat dieser Artikel gefallen. Viel Glück)
Andere Benchmarks
Mehrere andere Benchmarks zum Vergleich verschiedener Serializer
Als Microsoft System.Text.Json ankündigte , zeigten sie ihren eigenen Benchmark zum Vergleich von System.Text.Json und Newtonsoft.Json . Zusätzlich zur Serialisierung und Deserialisierung testet dieser Benchmark die Document-Klasse auf Direktzugriff, Reader und Writer. Sie demonstrierten auch ihren Query Per Second-Test , der mich dazu inspirierte, meinen eigenen zu erstellen.
Das .NET Core GitHub-Repository enthält eine Reihe von Benchmarks, die den in diesem Artikel beschriebenen ähnlich sind. Ich habe mir ihre Tests sehr genau angesehen, um sicherzustellen, dass ich selbst keine Fehler gemacht habe. Du findest sie inMicro-Benchmark-Lösung .
Jil hat seine eigenen Benchmarks , die Jil , Newtonsoft , Protobuf und ServiceStack vergleichen .
Utf8Json hat eine Reihe von Benchmarks veröffentlicht, die auf GitHub verfügbar sind . Sie testen auch binäre Serialisierer.
Alois Kraus hat die beliebtesten .NET-Serialisierer, einschließlich JSON-Serialisierer, binärer Serialisierer und XML-Serialisierer , hervorragend eingehend getestet . Der Benchmark umfasst Benchmarks für .NET Core 3 und .NET Framework 4.8.
Erfahren Sie mehr über den Kurs.