最近リリースされた.NETCore 3は、多くの革新をもたらしました。 C#8とWinFormsおよびWPFのサポートに加えて、最新リリースでは新しいJSON(de)シリアライザーSystem.Text.Jsonが追加されており、その名前が示すように、そのクラスはすべてこの名前名にあります。
これは大きな革新です。JSONシリアル化は、Webアプリケーションの重要な要素です。今日のRESTAPIのほとんどはそれに依存しています。 javascriptクライアントがPOSTリクエストの本文でJSONを送信すると、サーバーはJSON逆シリアル化を使用してそれをC#オブジェクトに変換します。また、サーバーが応答としてオブジェクトを返すと、javascriptクライアントが理解できるように、このオブジェクトをJSONにシリアル化します。これらは、オブジェクトを使用するすべての要求に対して実行される大規模な操作です。それらのパフォーマンスは、アプリケーションのパフォーマンスに大きな影響を与える可能性があります。これについては、これから説明します。
.NETの使用経験がある場合は、Newtonsoft.Jsonとしても知られる優れたJson.NETシリアライザーについて聞いたことがあるはずです。では、素敵な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パッケージ。複数の賞を受賞したライブラリ(私はおそらく確かに知りません)。
- 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 ( ).

- Utf8Jsonは、JSONシリアライザーに対するもう1つの自称最速のC#です。ゼロメモリ割り当てで動作し、パフォーマンスを向上させるためにUTF8バイナリに直接読み取り/書き込みします。

より高速な非JSONシリアライザーがあることに注意してください。特に、protobuf-netはバイナリシリアライザーであり、この記事で比較したどのシリアライザーよりも高速である必要があります(ただし、ベンチマークではテストされていません)。
ベンチマーク構造
シリアライザーを比較するのは簡単ではありません。シリアル化と逆シリアル化を比較する必要があります。さまざまなタイプのクラス(小規模および大規模)、リスト、および辞書を比較する必要があります。また、文字列、ストリーム、文字配列(UTF-8配列)など、さまざまなシリアル化ターゲットを比較する必要があります。これはかなり大きなテストマトリックスですが、できるだけ整理して簡潔にするように努めます。
4つの異なる機能をテストします。
- 文字列へのシリアル化
- ストリームへのシリアル化
- 文字列から逆シリアル化
- ASP.NET Core3アプリでの1秒あたりのリクエスト数
それぞれについて、さまざまなタイプのオブジェクトをテストします(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 Core3プロジェクトでのみ実行されます。
ベンチマーク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:ストリームへのシリアル化
テストの2番目のセットは、ストリームにシリアル化することを除いて、ほとんど同じです。ベンチマークコードはこちらです。結果:
より正確な数値はここにあります。System.Text.Jsonを機能させるのを手伝ってくれたAdamSitnikとAhsonKhanに感謝します。
結果は前のテストと非常に似ています。Utf8JsonとJilは他の4倍高速です。Jilは非常に高速で、Utf8Jsonに次ぐものです。DataContractJsonSerializerは、ほとんどの場合、依然として最も低速です。Newtonsoftは、Newtonsoftに顕著な利点がある辞書を除いて、ほとんどの場合System.Text.Jsonとほぼ同じように機能します。
ベンチマーク3:文字列からの逆シリアル化
次の一連のテストでは、文字列からの逆シリアル化を扱います。テストコードはここにあります。
より正確な数値はここにあります。
このベンチマークでDataContractJsonSerializerを 実行するのに問題があるため、結果に含まれていません。それ以外の場合は、Jilが逆シリアル化で最速であり、Utf8Jsonが2位であることがわかります。System.Text.Jsonより2〜3倍高速です。また、System.Text.JsonはJson.NETよりも約30%高速です。
これまでのところ、人気のあるNewtonsoft.Jsonと新しいSystem.Text.Jsonのパフォーマンスは、競合他社よりも大幅に劣っています。Newtonsoft.Jsonの人気により、これは私にとってかなり予想外の結果でした。そして新しいトップパフォーマーマイクロソフトの周りのすべての誇大宣伝System.Text.Json。ASP.NETアプリケーションで確認してみましょう。
ベンチマーク4:.NETサーバーでの1秒あたりのリクエスト数
前述のように、JSONシリアル化はREST APIに常に存在するため、非常に重要です。コンテンツタイプ
application/jsonを使用するサーバーへのHTTP要求は、 JSONオブジェクトをシリアル化または逆シリアル化する必要があります。サーバーがPOST要求でペイロードを受け入れると、サーバーはJSONから逆シリアル化します。サーバーが応答でオブジェクトを返すと、JSONをシリアル化します。最新のクライアントとサーバーの通信は、JSONシリアル化に大きく依存しています。したがって、「実際の」シナリオをテストするには、テストサーバーを作成してそのパフォーマンスを測定するのが理にかなっています。マイクロソフトのパフォーマンステスト
に触発されましたそこで彼らはMVCサーバーアプリケーションを作成し、1秒あたりのリクエストをチェックしました。MicrosoftベンチマークはSystem.Text.JsonとNewtonsoft.Jsonをテストします。この記事では、以前のテストで最速のシリアライザーの1つであることが証明されているUtf8Jsonと比較することを除いて、同じことを行います。
残念ながら、ASP.NET Core 3をJilと統合できなかったため、ベンチマークには含まれていません。もっと努力すればできると確信していますが、残念ながら。
このテストの作成は、以前よりも困難であることが判明しました。私は最初に、Microsoftベンチマークと同じように、ASP.NET Core 3.0MVCアプリを作成しました。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と、サーバーは1000SmallClassアイテムのリストを返し、それによってコンテンツを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 を切り替える方法が必要です。最初の2つで、それは簡単です。System.Text.Jsonあなたはまったく何もする必要はありません。Newtonsoft.Jsonに切り替えるには、次の行に1行追加するだけ
ConfigureServices です。
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews()
// Newtonsoft. - System.Text.Json
.AddNewtonsoftJson()
;
Utf8Json、我々は、カスタムメディアフォーマッタを追加する必要があります
InputFormatterとOutputFormatter。それほど簡単ではありませんでしたが、最終的にはインターネットで良い解決策を見つけ、設定を調べた後、うまくいきました。フォーマッタを含むNuGetパッケージもありますが、ASP.NET Core3では機能しません。
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#1秒あたりのリクエストを測定するためのクライアント
また、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は非常によくサポート。マイクロソフトは引き続きSystem.Text.Jsonにリソースと労力を投資して、優れたサポートを期待できるようにします。一方、ジルとUtf8Json昨年はほとんどコミットを受け取りませんでした。実際、過去6か月間はあまりメンテナンスが行われていないようです。
1つのオプションは、アプリケーションで複数のシリアライザーを組み合わせることです。より高速なシリアライザーにアップグレードしてASP.NETと統合し、優れたパフォーマンスを実現しますが、ビジネスロジックでNewtonsoft.Jsonを引き続き使用して、機能セットを最大限に活用してください。
この記事を楽しんでいただけたでしょうか。幸運を)
その他のベンチマーク
さまざまなシリアライザーを比較する他のいくつかのベンチマーク
MicrosoftがSystem.Text.Jsonを発表したとき、System.Text.JsonとNewtonsoft.Jsonを比較する独自のベンチマークを示しました。シリアル化と逆シリアル化に加えて、このベンチマークは、ランダムアクセス、リーダー、ライターについてDocumentクラスをテストします。彼らはまた、1秒あたりのクエリテストのデモも行いました。 .NET Core GitHubリポジトリには、この記事で説明したものと同様の一連のベンチマークが含まれています。私は彼らのテストを非常に綿密に調べて、自分で間違いを犯していないことを確認しました。あなたがそれらを見つけることができます
マイクロベンチマークソリューション。
Jilには、Jil、Newtonsoft、Protobuf、およびServiceStackを比較する独自のベンチマークがあります。 Utf8Jsonは、GitHubで利用可能な一連のベンチマークを投稿しました。また、バイナリシリアライザーもテストします。 Alois Krausは、JSONシリアライザー、バイナリシリアライザー、XMLシリアライザーなど、最も人気のある.NETシリアライザーの優れた詳細テストを実施しました。そのベンチマークには、.NET Core3および.NETFramework4.8のベンチマークが含まれています。
コースの詳細をご覧ください。