为啥使用 System.Text.Json 的 JSON 反序列化这么慢?
Posted
技术标签:
【中文标题】为啥使用 System.Text.Json 的 JSON 反序列化这么慢?【英文标题】:Why is JSON deserialisation with System.Text.Json so slow?为什么使用 System.Text.Json 的 JSON 反序列化这么慢? 【发布时间】:2021-11-24 01:33:27 【问题描述】:我有一个相同的最小项目,它将用 C# 和 Go 编写的 json 反序列化 100,000 次。性能差异很大。虽然很高兴知道使用 Go 可以实现性能目标,但我更愿意在 C# 中实现类似的结果。鉴于 C# 慢了 193 倍,我认为错误在我这边,但我不知道为什么。
性能
$ dotnet run .
real 1m37.555s
user 1m39.552s
sys 0m0.729s
$ ./jsonperf
real 0m0.478s
user 0m0.500s
sys 0m0.011s
C#源代码
using System;
namespace jsonperf
class Program
static void Main(string[] args)
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";
for (int i = 0; i < 100000; i++)
if (0 == i % 1000)
Console.WriteLine($"Completed: i");
var obj = BinanceTradeUpdate.FromJson(json);
Console.WriteLine("Done");
和
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace jsonperf
public class BinanceTradeUpdate
[JsonPropertyName("e")]
public string EventType
get;
set;
[JsonPropertyName("E")]
public long EventUnixTimestamp
get;
set;
[JsonIgnore]
public DateTime EventTime
get
return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(EventUnixTimestamp);
[JsonPropertyName("s")]
public string MarketSymbol
get;
set;
[JsonPropertyName("t")]
public long TradeId
get;
set;
[JsonPropertyName("p")]
public double Price
get;
set;
[JsonPropertyName("q")]
public double Quantity
get;
set;
[JsonPropertyName("b")]
public long BuyerOrderId
get;
set;
[JsonPropertyName("a")]
public long SellerOrderId
get;
set;
[JsonPropertyName("T")]
public long TradeUnixTimestamp
get;
set;
[JsonIgnore]
public DateTime TradeTime
get
return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(TradeUnixTimestamp);
[JsonPropertyName("m")]
public bool BuyerIsMarketMaker
get;
set;
[JsonPropertyName("M")]
public bool UndocumentedFlag
get;
set;
public static BinanceTradeUpdate FromJson(string json)
return JsonSerializer.Deserialize<BinanceTradeUpdate>(
json,
new JsonSerializerOptions()
NumberHandling = JsonNumberHandling.AllowReadingFromString
);
源码Go
package main
import (
"encoding/csv"
"encoding/json"
"fmt"
"os"
"strconv"
)
type Float64Str float64
func (f *Float64Str) UnmarshalJSON(b []byte) error
var s string
// Try to unmarshal string first
if err := json.Unmarshal(b, &s); err == nil
value, err := strconv.ParseFloat(s, 64)
if err != nil
return err
*f = Float64Str(value)
return nil
// If unsuccessful, unmarshal as float64
return json.Unmarshal(b, (*float64)(f))
// Trade represents an exchange of assets in a given market
type Trade struct
EventType string json:"e"
EventTime int64 json:"E"
MarketSymbol string json:"s"
TradeID int64 json:"t"
Price Float64Str json:"p"
Quantity Float64Str json:"q"
BuyerOrderID int64 json:"b"
SellerOrderID int64 json:"a"
TradeTime int64 json:"T"
IsBuyerMaker bool json:"m"
Flag bool json:"M"
func main()
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"
// open stdout
var stdwrite = csv.NewWriter(os.Stdout)
// convert string several times into obj
var trade = Trade
counter := 0
for i := 0; i < 100000; i++
if err := json.Unmarshal([]byte(jsonString), &trade); err != nil
stdwrite.Flush()
panic(err)
else
counter++
if counter%1000 == 0
fmt.Printf("%d elements read\n", counter)
【问题讨论】:
如果你想正确地进行基准测试,那么你需要使用基准测试工具。这将提供更公平的结果。此外,您需要删除所有控制台写入,这些写入非常慢并且会扭曲您的结果。您的 C# 代码也存在一些效率低下的问题,例如为每个交互创建一个新的 JsonSerializerOptions,而这应该是一个全局设置。 虽然你所说的一切都没有错,但请注意dotnet需要远远超过90s才能完成任务。向控制台打印 100 行,即 VM 初始化在这里是舍入错误。我之前通过将 .FromJson() 包装在对 System.Disgnostic.Stopwatch .Start() 和 .Stop() 的调用中进行了测试。相差不到 2 秒。 好吧,如果我运行你的代码,那么我需要大约 83 秒。如果我使用只初始化一次的 JsonSerializerOptions 则需要 232 毫秒。删除 Writeline 可以再节省约 30 毫秒,这意味着性能提升 > 12%(超过 232 毫秒)。 起首,我永远不会认为 JsonSerializerOptions 的初始化是昂贵的 ab 操作。这对我有很大帮助;非常感谢。 快速查看源代码,表明每种类型的元数据都缓存在JsonSerializerOptions
中。对于您的用例,静态缓存会更快,但这会阻止类型信息被垃圾收集。 github.com/dotnet/runtime/blob/main/src/libraries/…
【参考方案1】:
这需要这么长时间的原因是您每次都在初始化一个新的 JsonSerializerOptions
对象。
初始化序列化器一次,您将看到巨大的性能提升(对我来说是 70% 以上)。
【讨论】:
以上是关于为啥使用 System.Text.Json 的 JSON 反序列化这么慢?的主要内容,如果未能解决你的问题,请参考以下文章
使用 System.Text.Json 从 json 文件读取到 IEnumerable
使用 System.Text.Json 修改 JSON 文件