Quantcast
Channel: C#タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 9699

【C#】.NET標準のJSONシリアライザの選定方法+パフォーマンス比較の検討

$
0
0
概要 本記事は、.NETの標準的なJSONシリアライザ(下記3つ)を次の観点で調査したものです。 1.System.Text.Json 2.Newtonsoft.Json(左記は正式名です。以後はJSON.NETと表記) 3.DataContractJsonSerializer 調査の観点 ・「どのJSONシリアライザを選ぶべきか?」 ・「パフォーマンスはどうか?」 まとめ いきなりですが、先にまとめを書きます。 1. System.Text.Jsonを使えるなら、使う 理由は2つある。 ①Microsoft社はJSON.NETを共通フレームワークから除こうしている。 .NET Core 3.0の破壊的変更より抜粋 認証:Newtonsoft.Json 型の置き換え ASP.NET Core 3.0 では、Authentication API で使用される Newtonsoft.Json 型が System.Text.Json 型に置き換えられました。 次の場合を除き、Authentication パッケージの基本的な使用方法は影響を受けません。 dotnet/aspnetcore #7289より引用 As part of the ongoing effort to remove Newtonsoft.Json from the shared framework these types have now been replaced on the Authentication APIs. (機械翻訳)共有フレームワークからNewtonsoft.Jsonを削除するための継続的な取り組みの一環として、これらのタイプは認証APIで置き換えられました。 ②Microsoft社はDataContractJsonSerializerを推奨していない。 Microsoft社:DataContractJsonSerializerのページより引用 JSON へのシリアル化と JSON からの逆シリアル化を含むほとんどのシナリオでは、 名前空間の System.Text.Jsonのapiを使用することをお勧めします。 サポートバージョンの一覧 Microsoft社 JSONシリアル化 概要のライブラリ入手方法より引用 .NET Standard 2.0 以降のバージョン .NET Framework 4.7.2 以降のバージョン .NET Core 2.0、2.1、および 2.2 2 System.Text.Jsonを使えない場合、JSON.NETを使う System.Text.Jsonをサポートしないバージョンの場合は、JSON.NETの使用を考える。 書籍「実践で役立つC#プログラミングのイディオム/定石&パターン」P317 より引用 ASP.NET MVCではNetsoftのJSON.NETが標準でプロジェクトに組み込まれています。そのため、ASP.NET MVCの場合は、DataContractJsonSerializerではなく、JSON.NETを使い、JSON形式のシリアル化/逆シリアル化を行うのが一般的です。 ASP.NET MVCでなくてもJSON.NETを使うことができますので、可能であればJSON.NETの利用も検討してみてください。 JSON.NETの使い方は下記が参考になります。 @IT JSONデータを作成/解析するには?[C#/VB] 3.System.Text.Jsonは使い方次第で、パフォーマンスが低下する Json文字列を日本語で出力させるために、System.Text.Jsonにエスケープを抑止の設定(※)を追加したところ、抑止の設定を追加しな場合に比べて、パフォーマンスが低下した。 System.Text.Json以外のライブラリと比較しても、抑止設定を追加した場合のパフォーマンスは他に比べて低い。 ① 実行速度の低下(下記表のmeanを参照。) ② メモリ使用量の増加(下記表のAllocatedを参照) ※System.Text.Json で文字エンコードをカスタマイズする方法より 既定では、シリアライザーでは ASCII 以外のすべての文字がエスケープされます。 つまり、\uxxxx に置き換えられます。xxxx は文字の Unicode コードです。 Method Mean StdDev Allocated DataContractJsonSerializer 8.887 us 0.1165 us 14152 B JSON.Net 3.912 us 0.0457 us 4304 B SystemTextJson(エスケープ抑止あり) 889.540 us 8.6991 us 44213 B SystemTextJson(エスケープ抑止なし) 3.106 us 0.0236 us 672 B この結果は下記の手順を基に作成したものです。 (a) 3つのライブラリを用いて、「同一データに対してシリアライズ→デシリアライズ」を行う関数を作成した。 (b) 前記の関数に、最終的な出力が同じになるように調整を加えた。 (c) benchMarkdotNetで計測を開始した。 (d) 結果を比較する。 動作環境について .NET Core 3.1.8 BenchmarkDotNet V0.12.1 JSON.NET(Newtonsoft.Json) V13.0.1 OS: Windows 10.0.19041.867 Intel Core i7-7700HQ CPU 2.80GHz パフォーマンス計測に関して 方針 「計測対象の最終出力結果が同一になる関数」を作成して、それぞれを比較する。 同一の入力データに対して、シリアライズ後にデシリアライズする。 それぞれをコンソール出力した時に下記の通りになるように調整する(※)。 ※計測時にはコンソール出力しない。 //調整結果 //シリアライズ後 {"company":"株式会社 自宅警備","department":"開発部","name":"山本太郎","sex":"男","age":30} //デシリアライズ後 company=株式会社 自宅警備,department=開発部,name=山本太郎,sex=男,age=30 遭遇した問題:System.Text.Jsonが日本語表記されない シリアライズ後、Unicodeコードで出力される。 //シリアライズ Unicodeを日本語にエンコードできてない {"Company":"\u682A\u5F0F\u4F1A\u793E \u81EA\u5B85\u8B66\u5099","Department":"\u958B\u767A\u90E8","Name":"\u5C71\u672C\u592A\u90CE","Sex":"\u7537","Age":30} //デシリアライズ company=株式会社 自宅警備,department=開発部,name=山本太郎,sex=男,age=30 原因は以下。 System.Text.Json で文字エンコードをカスタマイズする方法より 既定では、シリアライザーでは ASCII 以外のすべての文字がエスケープされます。 つまり、\uxxxx に置き換えられます。xxxx は文字の Unicode コードです。 すべての言語セットをエスケープせずにシリアル化するには、UnicodeRanges.All を使用します。 下記サイトを参考にエスケープを抑止するオプション指定を追加した。 .NET Core:JsonSerializerの実践的な使い方 また、計測の観点にオプション指定の有無を追加することを決めた。 コード 計測対象 using System; using BenchmarkDotNet.Attributes; using System.IO; using System.Runtime.Serialization; using System.Text; using System.Text.Json; using System.Text.Unicode; using System.Runtime.Serialization.Json; using System.Text.Encodings.Web; namespace StudyJsonSerializer { [MemoryDiagnoser] public class MeasurementJsonSerializer { /// <summary> /// DataContractJsonSerializerによるシリアル化/デシリアル化のサンプル /// </summary> [Benchmark] public void UsingDataContractJsonSerializer() { //シリアル化 using var serializerStream = new MemoryStream(); var serializer = new DataContractJsonSerializer(OfficeWorkerContract.GetType()); serializer.WriteObject(serializerStream, OfficeWorkerContract); var jsonString = Encoding.UTF8.GetString(serializerStream.ToArray()); //デシリアル化 var byteArray = Encoding.UTF8.GetBytes(jsonString); using var deserializerStreamtream = new MemoryStream(byteArray); var deserializer = new DataContractJsonSerializer(typeof(OfficeWorkerContractModel)); var officeWorker = serializer.ReadObject(deserializerStreamtream) as OfficeWorkerContractModel; } /// <summary> /// Json.NETによるシリアル化/デシリアル化のサンプル /// </summary> [Benchmark] public void UsingJsonNet() { var jsonString = Newtonsoft.Json.JsonConvert.SerializeObject(OfficeWorker); var result = Newtonsoft.Json.JsonConvert.DeserializeObject<OfficeWorker>(jsonString); } /// <summary> /// System.Text.Json.Serializationによるシリアル化/デシリアル化のサンプル /// </summary> [Benchmark] public void UsingSystemTextJson() { var options = new JsonSerializerOptions() { // すべての言語セットをエスケープせずにシリアル化させる Encoder = JavaScriptEncoder.Create(UnicodeRanges.All) }; var jsonString = System.Text.Json.JsonSerializer.Serialize(OfficeWorker, options); var result = System.Text.Json.JsonSerializer.Deserialize<OfficeWorker>(jsonString); } /// <summary> /// System.Text.Json.Serializationによるシリアル化/デシリアル化のオプション指定を外す /// </summary> [Benchmark] public void UsingSystemTextJsonWithoutSerializeOption() { var jsonString = System.Text.Json.JsonSerializer.Serialize(OfficeWorker); var result = System.Text.Json.JsonSerializer.Deserialize<OfficeWorker>(jsonString); } /// <summary> /// DataContractJsonSerializer用(他と異なるため念の為、分ける) /// </summary> private static readonly OfficeWorker OfficeWorker = new OfficeWorker() { Company = "株式会社 自宅警備", Department = "開発部", Name = "山本太郎", Sex = "男", Age = 30, }; /// <summary> /// DataContractJsonSerializer以外で使用 /// </summary> private static readonly OfficeWorkerContractModel OfficeWorkerContract = new OfficeWorkerContractModel() { Company = "株式会社 自宅警備", Department = "開発部", Name = "山本太郎", Sex = "男", Age = 30, }; } /// <summary> /// DataContractJsonSerializer用の会社員モデル(属性指定が必要なため、念の為分ける) /// </summary> [DataContract(Name = "officeWorker")] public class OfficeWorkerContractModel { /// <summary> /// 会社 /// </summary> [DataMember(Name = "company",Order = 0)] public string Company { get; set; } /// <summary> /// 部署 /// </summary> [DataMember(Name = "department", Order = 1)] public string Department { get; set; } /// <summary> /// 名前 /// </summary> [DataMember(Name = "name", Order = 2)] public string Name { get; set; } /// <summary> /// 性別 /// </summary> [DataMember(Name = "sex", Order = 3)] public string Sex { get; set; } /// <summary> /// 年齢 /// </summary> [DataMember(Name = "age", Order = 4)] public uint Age { get; set; } /// <summary> /// 文字列変換 /// </summary> /// <returns></returns> public override string ToString() { return $"company={Company},department={Department},name={Name},sex={Sex},age={Age}"; } } /// <summary> /// 会社員(DataContractJsonSerializer以外で使用) /// </summary> public class OfficeWorker { /// <summary> /// 会社 /// </summary> public string Company { get; set; } /// <summary> /// 部署 /// </summary> public string Department { get; set; } /// <summary> /// 名前 /// </summary> public string Name { get; set; } /// <summary> /// 性別 /// </summary> public string Sex { get; set; } /// <summary> /// 年齢 /// </summary> public uint Age { get; set; } /// <summary> /// 文字列変換 /// </summary> /// <returns></returns> public override string ToString() { return $"company={Company},department={Department},name={Name},sex={Sex},age={Age}"; } } } より正確に測るなら、もっと自前の実装を極力排除すべきである。しかし、そこまでやり切る技量は私にはない。 メイン関数 using BenchmarkDotNet.Running; namespace StudyJsonSerializer { class Program { static void Main( string[] args ) { //計測開始 var summary = BenchmarkRunner.Run<MeasurementJsonSerializer>(); } } } 測定結果のサマリ Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated UsingDataContractJsonSerializer 8.887 us 0.1315 us 0.1165 us 4.5013 - - 14152 B UsingJsonNet 3.912 us 0.0515 us 0.0457 us 1.3695 - - 4304 B UsingSystemTextJson 889.540 us 11.1422 us 8.6991 us 15.6250 7.8125 - 44213 B UsingSystemTextJsonWithoutSerializeOption 3.106 us 0.0252 us 0.0236 us 0.2136 - - 672 B ラベルの説明は下記の通り Mean : Arithmetic mean of all measurements Error : Half of 99.9% confidence interval StdDev : Standard deviation of all measurements Gen 0 : GC Generation 0 collects per 1000 operations Gen 1 : GC Generation 1 collects per 1000 operations Gen 2 : GC Generation 2 collects per 1000 operations Allocated : Allocated memory per single operation (managed only, inclusive, 1KB = 1024B) 1 us : 1 Microsecond (0.000001 sec) 測定結果の詳細(一応控える) // * Detailed results * MeasurementJsonSerializer.UsingDataContractJsonSerializer: DefaultJob Runtime = .NET Core 3.1.8 (CoreCLR 4.700.20.41105, CoreFX 4.700.20.41903), X64 RyuJIT; GC = Concurrent Workstation Mean = 8.887 us, StdErr = 0.031 us (0.35%), N = 14, StdDev = 0.117 us Min = 8.754 us, Q1 = 8.792 us, Median = 8.873 us, Q3 = 8.940 us, Max = 9.203 us IQR = 0.148 us, LowerFence = 8.570 us, UpperFence = 9.162 us ConfidenceInterval = [8.756 us; 9.019 us] (CI 99.9%), Margin = 0.131 us (1.48% of Mean) Skewness = 1.17, Kurtosis = 4.18, MValue = 2 -------------------- Histogram -------------------- [8.691 us ; 8.977 us) | @@@@@@@@@@@@ [8.977 us ; 9.266 us) | @@ --------------------------------------------------- MeasurementJsonSerializer.UsingJsonNet: DefaultJob Runtime = .NET Core 3.1.8 (CoreCLR 4.700.20.41105, CoreFX 4.700.20.41903), X64 RyuJIT; GC = Concurrent Workstation Mean = 3.912 us, StdErr = 0.012 us (0.31%), N = 14, StdDev = 0.046 us Min = 3.848 us, Q1 = 3.878 us, Median = 3.912 us, Q3 = 3.934 us, Max = 4.004 us IQR = 0.056 us, LowerFence = 3.795 us, UpperFence = 4.018 us ConfidenceInterval = [3.860 us; 3.963 us] (CI 99.9%), Margin = 0.052 us (1.32% of Mean) Skewness = 0.32, Kurtosis = 2.08, MValue = 2 -------------------- Histogram -------------------- [3.823 us ; 4.029 us) | @@@@@@@@@@@@@@ --------------------------------------------------- MeasurementJsonSerializer.UsingSystemTextJson: DefaultJob Runtime = .NET Core 3.1.8 (CoreCLR 4.700.20.41105, CoreFX 4.700.20.41903), X64 RyuJIT; GC = Concurrent Workstation Mean = 889.540 us, StdErr = 2.511 us (0.28%), N = 12, StdDev = 8.699 us Min = 875.936 us, Q1 = 883.029 us, Median = 890.843 us, Q3 = 895.243 us, Max = 904.026 us IQR = 12.213 us, LowerFence = 864.709 us, UpperFence = 913.563 us ConfidenceInterval = [878.398 us; 900.682 us] (CI 99.9%), Margin = 11.142 us (1.25% of Mean) Skewness = -0.15, Kurtosis = 1.7, MValue = 2 -------------------- Histogram -------------------- [870.949 us ; 909.013 us) | @@@@@@@@@@@@ --------------------------------------------------- MeasurementJsonSerializer.UsingSystemTextJsonWithoutSerializeOption: DefaultJob Runtime = .NET Core 3.1.8 (CoreCLR 4.700.20.41105, CoreFX 4.700.20.41903), X64 RyuJIT; GC = Concurrent Workstation Mean = 3.106 us, StdErr = 0.006 us (0.20%), N = 15, StdDev = 0.024 us Min = 3.052 us, Q1 = 3.097 us, Median = 3.104 us, Q3 = 3.122 us, Max = 3.142 us IQR = 0.025 us, LowerFence = 3.060 us, UpperFence = 3.160 us ConfidenceInterval = [3.081 us; 3.131 us] (CI 99.9%), Margin = 0.025 us (0.81% of Mean) Skewness = -0.49, Kurtosis = 2.76, MValue = 2 -------------------- Histogram -------------------- [3.039 us ; 3.155 us) | @@@@@@@@@@@@@@@ --------------------------------------------------- 執筆の際に参考にしたサイト/書籍 .NET Core 3.0の破壊的変更 dotnet/aspnetcore #7289 Microsoft社:DataContractJsonSerializer Microsoft社 JSONシリアル化 概要 書籍:実践で役立つC#プログラミングのイディオム/定石&パターン 12.3 JSONデータのシリアル化と逆シリアル化 @IT JSONデータを作成/解析するには?[C#/VB] System.Text.Json で文字エンコードをカスタマイズする方法 .NET 内で JSON のシリアル化と逆シリアル化 (マーシャリングとマーシャリングの解除) を行う方法 .NET Core:JsonSerializerの実践的な使い方 Newtonsoft.Json と System.Text.Json の相違点の表 【.NET/C#】メソッドのパフォーマンスを簡単に集計するライブラリの紹介

Viewing all articles
Browse latest Browse all 9699

Trending Articles