9
0

More than 1 year has passed since last update.

はじめに

System.Text.Jsonが、.NET 6.0よりSourceGeneratorによる、リフレクション無しのシリアライズをサポートするようになった
そこで、従来のやり方であるリフレクションベースのやり方と、SourceGeneratorベースのやり方で、どの程度のパフォーマンスの違いが出るのか、検証してみた。

ソース生成のやり方

やり方自体は 公式ガイド もあるので詳しくは説明しないが、大まかにいうと

  1. シリアライズしたい型を作る
  2. System.Text.Json.Serialization.JsonSerializerContext を継承したpartialクラスを作る
  3. System.Text.Json.Serialization.JsonSerializableAttribute を上記で作成したクラスに追加して、引数にシリアライズ対象の型をtypeofで渡す

とすると、JsonSerializerContextを継承したクラスに JsonTypeInfo<[シリアライズしたい型]> Default.[シリアライズしたい型] というものが追加されるので、
それを JsonSerializer.Serialize<T>JsonSerializer.Deserialize<T> に引数として渡せばOK。

結果

BenchmarkDotNetで計測した結果を以下に記述する。なお、今回使用したソースはgistにアップロードしておいた。
結果に対する考察は後述。

シリアライズ(JsonSerializeBenchmark)

単純な型のシリアライズベンチで、SerializeCodeGenがSourceGeneratorを使った方で、SerializeClassicが従来のやり方で行ったもの


BenchmarkDotNet=v0.13.1, OS=Windows 10.0.19043.1348 (21H1/May2021Update)
Intel Core i7-9700 CPU 3.00GHz, 1 CPU, 8 logical and 8 physical cores
.NET SDK=6.0.100
  [Host]   : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT
  ShortRun : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT

Job=ShortRun  IterationCount=3  LaunchCount=1  
WarmupCount=3  

Method Mean Error StdDev Gen 0 Allocated
SerializeCodeGen 209.6 ns 35.72 ns 1.96 ns 0.0484 304 B
SerializeClassic 275.4 ns 11.96 ns 0.66 ns 0.0482 304 B

デシリアライズ(JsonDeserializeBenchmark)

JSON文字列から単純な型のインスタンスを生成するベンチマーク。


BenchmarkDotNet=v0.13.1, OS=Windows 10.0.19043.1348 (21H1/May2021Update)
Intel Core i7-9700 CPU 3.00GHz, 1 CPU, 8 logical and 8 physical cores
.NET SDK=6.0.100
  [Host]   : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT
  ShortRun : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT

Job=ShortRun  IterationCount=3  LaunchCount=1  
WarmupCount=3  

Method Mean Error StdDev Gen 0 Allocated
DeserializeCodeGen 399.4 ns 59.15 ns 3.24 ns 0.0114 72 B
DeserializeClassic 393.3 ns 77.47 ns 4.25 ns 0.0114 72 B

複雑な型

型の中に別の型が更に入れ子で入っていた場合のベンチマーク


BenchmarkDotNet=v0.13.1, OS=Windows 10.0.19043.1348 (21H1/May2021Update)
Intel Core i7-9700 CPU 3.00GHz, 1 CPU, 8 logical and 8 physical cores
.NET SDK=6.0.100
  [Host]   : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT
  ShortRun : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT

Job=ShortRun  IterationCount=3  LaunchCount=1  
WarmupCount=3  

Method Mean Error StdDev Gen 0 Allocated
SerializeCodeGen 303.6 ns 75.45 ns 4.14 ns 0.0625 392 B
SerializeClassic 542.2 ns 25.10 ns 1.38 ns 0.1173 736 B
DeserializeCodeGen 798.6 ns 157.66 ns 8.64 ns 0.0877 552 B
DeserializeClassic 758.8 ns 143.35 ns 7.86 ns 0.0877 552 B

考察

シリアライズに関して言えば、ソース生成の方が高速と言えそうだが、意外な結果として、デシリアライズの方は従来方式の方が性能が良かった。
複雑な型になるほどこの傾向はより顕著になっていったと言えるので、単純にソース生成が最適解というわけではなさそう。

これに関して、生成されたソースを見ると、シリアライズの方は機械的にWriterのメソッドを並べるだけになっているのに対し、
デシリアライズの方は、 JsonMetadataServices.CreateValueInfo<T> で作成しており、ここで作るJsonTypeInfoが、リフレクションベースで作っているものに比べて効率が悪い部分があるのではないかと思われる。
ループで複数回作ったり予めキャッシュを作らせるようにしても、特に結果は変わらなかった。
この辺りは、ソース生成だからと言っても、結局実装次第という話。

おわりに

デシリアライズの結果は意外だったが、ソース生成型の方は、パフォーマンスだけではなく、リフレクションレスという事自体が
そもそもかなり利点になっていると言えるので、iOS等、リフレクションに大幅な制限がかけられている領域ではかなり大きな助けになると思う。
また、net7.0で採用予定のNativeAOTでもリフレクションは限定的にしか使えないものになるので、将来的にもっと重要な役割を担うかもしれない。
デシリアライズの結果について、なぜそうなるかという事は宿題にする。

参考リンク

  • JsonSrcGen
    • ソースジェネレーターベースのJSONシリアライザ
    • 機能的には非常に限定されている(フィールドにサポートされている以外の型があると面倒)が、性能面では圧倒的
  • 公式の実装
9
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
0