为什么使用System.Text.Json进行JSON反序列化如此慢?

huangapple go评论125阅读模式
英文:

Why is JSON deserialisation with System.Text.Json so slow?

问题

我有一个相同的最小项目,用C#和Go分别写了一个反序列化json 100,000次的程序。性能差异很大。虽然使用Go可以实现性能目标,但我更希望在C#中实现可比较的结果。考虑到C#的速度慢了193倍,我认为错误可能出在我这边,但我无法找出原因。

以下是C#的性能测试结果:

  1. $ dotnet run .
  2. real 1m37.555s
  3. user 1m39.552s
  4. sys 0m0.729s
  5. $ ./jsonperf
  6. real 0m0.478s
  7. user 0m0.500s
  8. sys 0m0.011s

以下是C#的源代码:

  1. using System;
  2. namespace jsonperf
  3. {
  4. class Program
  5. {
  6. static void Main(string[] args)
  7. {
  8. var json = "{\"e\":\"trade\",\"E\":1633046399882,\"s\":\"BTCBUSD\",\"t\":243216662,\"p\":\"43818.22000000\",\"q\":\"0.00452000\",\"b\":3422298876,\"a\":3422298789,\"T\":1633046399882,\"m\":false,\"M\":true}";
  9. for (int i = 0; i < 100000; i++)
  10. {
  11. if (0 == i % 1000)
  12. {
  13. Console.WriteLine($"Completed: {i}");
  14. }
  15. var obj = BinanceTradeUpdate.FromJson(json);
  16. }
  17. Console.WriteLine("Done");
  18. }
  19. }
  20. }

  1. using System;
  2. using System.Text.Json;
  3. using System.Text.Json.Serialization;
  4. namespace jsonperf
  5. {
  6. public class BinanceTradeUpdate
  7. {
  8. [JsonPropertyName("e")]
  9. public string EventType
  10. {
  11. get;
  12. set;
  13. }
  14. [JsonPropertyName("E")]
  15. public long EventUnixTimestamp
  16. {
  17. get;
  18. set;
  19. }
  20. [JsonIgnore]
  21. public DateTime EventTime
  22. {
  23. get
  24. {
  25. return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(EventUnixTimestamp);
  26. }
  27. }
  28. [JsonPropertyName("s")]
  29. public string MarketSymbol
  30. {
  31. get;
  32. set;
  33. }
  34. [JsonPropertyName("t")]
  35. public long TradeId
  36. {
  37. get;
  38. set;
  39. }
  40. [JsonPropertyName("p")]
  41. public double Price
  42. {
  43. get;
  44. set;
  45. }
  46. [JsonPropertyName("q")]
  47. public double Quantity
  48. {
  49. get;
  50. set;
  51. }
  52. [JsonPropertyName("b")]
  53. public long BuyerOrderId
  54. {
  55. get;
  56. set;
  57. }
  58. [JsonPropertyName("a")]
  59. public long SellerOrderId
  60. {
  61. get;
  62. set;
  63. }
  64. [JsonPropertyName("T")]
  65. public long TradeUnixTimestamp
  66. {
  67. get;
  68. set;
  69. }
  70. [JsonIgnore]
  71. public DateTime TradeTime
  72. {
  73. get
  74. {
  75. return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(TradeUnixTimestamp);
  76. }
  77. }
  78. [JsonPropertyName("m")]
  79. public bool BuyerIsMarketMaker
  80. {
  81. get;
  82. set;
  83. }
  84. [JsonPropertyName("M")]
  85. public bool UndocumentedFlag
  86. {
  87. get;
  88. set;
  89. }
  90. public static BinanceTradeUpdate FromJson(string json)
  91. {
  92. return JsonSerializer.Deserialize<BinanceTradeUpdate>(
  93. json,
  94. new JsonSerializerOptions()
  95. {
  96. NumberHandling = JsonNumberHandling.AllowReadingFromString
  97. });
  98. }
  99. }
  100. }

以下是Go的源代码:

  1. package main
  2. import (
  3. "encoding/csv"
  4. "encoding/json"
  5. "fmt"
  6. "os"
  7. "strconv"
  8. )
  9. type Float64Str float64
  10. func (f *Float64Str) UnmarshalJSON(b []byte) error {
  11. var s string
  12. // 尝试首先解析为字符串
  13. if err := json.Unmarshal(b, &s); err == nil {
  14. value, err := strconv.ParseFloat(s, 64)
  15. if err != nil {
  16. return err
  17. }
  18. *f = Float64Str(value)
  19. return nil
  20. }
  21. // 如果不成功,则解析为float64
  22. return json.Unmarshal(b, (*float64)(f))
  23. }
  24. // Trade 表示在给定市场上的资产交换
  25. type Trade struct {
  26. EventType string `json:"e"`
  27. EventTime int64 `json:"E"`
  28. MarketSymbol string `json:"s"`
  29. TradeID int64 `json:"t"`
  30. Price Float64Str `json:"p"`
  31. Quantity Float64Str `json:"q"`
  32. BuyerOrderID int64 `json:"b"`
  33. SellerOrderID int64 `json:"a"`
  34. TradeTime int64 `json:"T"`
  35. IsBuyerMaker bool `json:"m"`
  36. Flag bool `json:"M"`
  37. }
  38. func main() {
  39. jsonString := "{\"e\":\"trade\",\"E\":1633046399882,\"s\":\"BTCBUSD\",\"t\":243216662,\"p\":\"43818.22000000\",\"q\":\"0.00452000\",\"b\":3422298876,\"a\":3422298789,\"T\":1633046399882,\"m\":false,\"M\":true}"
  40. // 打开标准输出
  41. var stdwrite = csv.NewWriter(os.Stdout)
  42. // 将字符串多次转换为对象
  43. var trade = Trade{}
  44. counter := 0
  45. for i := 0; i < 100000; i++ {
  46. if err := json.Unmarshal([]byte(jsonString), &trade); err != nil {
  47. stdwrite.Flush()
  48. panic(err)
  49. } else {
  50. counter++
  51. if counter%1000 == 0 {
  52. fmt.Printf("%d elements read\n", counter)
  53. }
  54. }
  55. }
  56. }
英文:

I have the same minimal project that deserializes a json 100,000 times written in C# and in Go. The performance varies greatly. While it is nice to know that performance goals can be achieved by using Go, I would much prefer to achieve comparable results in C#. Given that C# is 193x slower, I assume the mistake is on my side, but I cannot figure out why.

Performance

  1. $ dotnet run .
  2. real 1m37.555s
  3. user 1m39.552s
  4. sys 0m0.729s
  5. $ ./jsonperf
  6. real 0m0.478s
  7. user 0m0.500s
  8. sys 0m0.011s

Source code C#

  1. using System;
  2. namespace jsonperf
  3. {
  4. class Program
  5. {
  6. static void Main(string[] args)
  7. {
  8. var json = &quot;{\&quot;e\&quot;:\&quot;trade\&quot;,\&quot;E\&quot;:1633046399882,\&quot;s\&quot;:\&quot;BTCBUSD\&quot;,\&quot;t\&quot;:243216662,\&quot;p\&quot;:\&quot;43818.22000000\&quot;,\&quot;q\&quot;:\&quot;0.00452000\&quot;,\&quot;b\&quot;:3422298876,\&quot;a\&quot;:3422298789,\&quot;T\&quot;:1633046399882,\&quot;m\&quot;:false,\&quot;M\&quot;:true}&quot;;
  9. for (int i = 0; i &lt; 100000; i++)
  10. {
  11. if (0 == i % 1000)
  12. {
  13. Console.WriteLine($&quot;Completed: {i}&quot;);
  14. }
  15. var obj = BinanceTradeUpdate.FromJson(json);
  16. }
  17. Console.WriteLine(&quot;Done&quot;);
  18. }
  19. }
  20. }

and

  1. using System;
  2. using System.Text.Json;
  3. using System.Text.Json.Serialization;
  4. namespace jsonperf
  5. {
  6. public class BinanceTradeUpdate
  7. {
  8. [JsonPropertyName(&quot;e&quot;)]
  9. public string EventType
  10. {
  11. get;
  12. set;
  13. }
  14. [JsonPropertyName(&quot;E&quot;)]
  15. public long EventUnixTimestamp
  16. {
  17. get;
  18. set;
  19. }
  20. [JsonIgnore]
  21. public DateTime EventTime
  22. {
  23. get
  24. {
  25. return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(EventUnixTimestamp);
  26. }
  27. }
  28. [JsonPropertyName(&quot;s&quot;)]
  29. public string MarketSymbol
  30. {
  31. get;
  32. set;
  33. }
  34. [JsonPropertyName(&quot;t&quot;)]
  35. public long TradeId
  36. {
  37. get;
  38. set;
  39. }
  40. [JsonPropertyName(&quot;p&quot;)]
  41. public double Price
  42. {
  43. get;
  44. set;
  45. }
  46. [JsonPropertyName(&quot;q&quot;)]
  47. public double Quantity
  48. {
  49. get;
  50. set;
  51. }
  52. [JsonPropertyName(&quot;b&quot;)]
  53. public long BuyerOrderId
  54. {
  55. get;
  56. set;
  57. }
  58. [JsonPropertyName(&quot;a&quot;)]
  59. public long SellerOrderId
  60. {
  61. get;
  62. set;
  63. }
  64. [JsonPropertyName(&quot;T&quot;)]
  65. public long TradeUnixTimestamp
  66. {
  67. get;
  68. set;
  69. }
  70. [JsonIgnore]
  71. public DateTime TradeTime
  72. {
  73. get
  74. {
  75. return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(TradeUnixTimestamp);
  76. }
  77. }
  78. [JsonPropertyName(&quot;m&quot;)]
  79. public bool BuyerIsMarketMaker
  80. {
  81. get;
  82. set;
  83. }
  84. [JsonPropertyName(&quot;M&quot;)]
  85. public bool UndocumentedFlag
  86. {
  87. get;
  88. set;
  89. }
  90. public static BinanceTradeUpdate FromJson(string json)
  91. {
  92. return JsonSerializer.Deserialize&lt;BinanceTradeUpdate&gt;(
  93. json,
  94. new JsonSerializerOptions()
  95. {
  96. NumberHandling = JsonNumberHandling.AllowReadingFromString
  97. });
  98. }
  99. }
  100. }

Source code Go

  1. package main
  2. import (
  3. &quot;encoding/csv&quot;
  4. &quot;encoding/json&quot;
  5. &quot;fmt&quot;
  6. &quot;os&quot;
  7. &quot;strconv&quot;
  8. )
  9. type Float64Str float64
  10. func (f *Float64Str) UnmarshalJSON(b []byte) error {
  11. var s string
  12. // Try to unmarshal string first
  13. if err := json.Unmarshal(b, &amp;s); err == nil {
  14. value, err := strconv.ParseFloat(s, 64)
  15. if err != nil {
  16. return err
  17. }
  18. *f = Float64Str(value)
  19. return nil
  20. }
  21. // If unsuccessful, unmarshal as float64
  22. return json.Unmarshal(b, (*float64)(f))
  23. }
  24. // Trade represents an exchange of assets in a given market
  25. type Trade struct {
  26. EventType string json:&quot;e&quot;
  27. EventTime int64 json:&quot;E&quot;
  28. MarketSymbol string json:&quot;s&quot;
  29. TradeID int64 json:&quot;t&quot;
  30. Price Float64Str json:&quot;p&quot;
  31. Quantity Float64Str json:&quot;q&quot;
  32. BuyerOrderID int64 json:&quot;b&quot;
  33. SellerOrderID int64 json:&quot;a&quot;
  34. TradeTime int64 json:&quot;T&quot;
  35. IsBuyerMaker bool json:&quot;m&quot;
  36. Flag bool json:&quot;M&quot;
  37. }
  38. func main() {
  39. jsonString := &quot;{\&quot;e\&quot;:\&quot;trade\&quot;,\&quot;E\&quot;:1633046399882,\&quot;s\&quot;:\&quot;BTCBUSD\&quot;,\&quot;t\&quot;:243216662,\&quot;p\&quot;:\&quot;43818.22000000\&quot;,\&quot;q\&quot;:\&quot;0.00452000\&quot;,\&quot;b\&quot;:3422298876,\&quot;a\&quot;:3422298789,\&quot;T\&quot;:1633046399882,\&quot;m\&quot;:false,\&quot;M\&quot;:true}&quot;
  40. // open stdout
  41. var stdwrite = csv.NewWriter(os.Stdout)
  42. // convert string several times into obj
  43. var trade = Trade{}
  44. counter := 0
  45. for i := 0; i &lt; 100000; i++ {
  46. if err := json.Unmarshal([]byte(jsonString), &amp;trade); err != nil {
  47. stdwrite.Flush()
  48. panic(err)
  49. } else {
  50. counter++
  51. if counter%1000 == 0 {
  52. fmt.Printf(&quot;%d elements read\n&quot;, counter)
  53. }
  54. }
  55. }
  56. }

答案1

得分: 13

这需要很长时间的原因是每次都在初始化一个新的JsonSerializerOptions对象。

只需初始化一次序列化器,你将看到巨大的性能提升(对我来说超过70%)。

英文:

The reason this takes so long is that you’re initialising a new JsonSerializerOptions object everytime.

Initialise the serialiser once & you’ll see huge performance improvements (70%+ for me).

huangapple
  • 本文由 发表于 2021年10月3日 13:00:36
  • 转载请务必保留本文链接:https://go.coder-hub.com/69422196.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定