为何C#使用System.Text.Json执行JSON反序列化性能远低于Go?
性能差异的核心原因
你目前的C#实现存在两个严重影响性能的低级错误,和Go的测试条件也不对等,才会出现上百倍的性能差距:
- 重复实例化
JsonSerializerOptions:你每次调用FromJson方法都会新建一个JsonSerializerOptions对象,这个实例的初始化会触发对目标类的反射扫描、序列化元数据构建等重开销操作,10万次循环等于重复执行了10万次元数据生成逻辑,这部分占了99%以上的耗时。JsonSerializerOptions是线程安全的,完全可以静态缓存复用,不需要每次新建。 - 测试构建类型不对等:你直接执行
dotnet run .默认运行的是Debug构建,没有开启代码优化,还附带调试符号,性能本身就比Release构建低3~10倍不等,而你运行的Go二进制是经过编译优化的Release版本,测试条件完全不对等。
优化方案
1. 缓存序列化配置
修改BinanceTradeUpdate类的FromJson方法,把JsonSerializerOptions改为静态只读对象复用:
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 => 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 => 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; } // 新增:静态缓存序列化配置,全局只初始化一次 private static readonly JsonSerializerOptions _jsonSerializeOptions = new JsonSerializerOptions() { NumberHandling = JsonNumberHandling.AllowReadingFromString }; public static BinanceTradeUpdate FromJson(string json) { // 复用静态配置,不再每次新建 return JsonSerializer.Deserialize<BinanceTradeUpdate>(json, _jsonSerializeOptions); } } }
2. 用Release模式运行测试
执行以下命令运行优化后的Release版本:
dotnet run --configuration Release
3. 可选:使用源生成器进一步优化
如果要完全消除运行时反射开销,可以使用System.Text.Json提供的源生成器功能,在编译阶段就生成好序列化/反序列化代码:
首先将BinanceTradeUpdate改为partial类,加上JsonSerializable特性:
[JsonSerializable(typeof(BinanceTradeUpdate))] public partial class BinanceTradeUpdate { // 原有属性代码不变 }
然后修改FromJson方法使用预生成的序列化上下文:
public static BinanceTradeUpdate FromJson(string json) { return JsonSerializer.Deserialize(json, JsonContext.Default.BinanceTradeUpdate); }
优化后效果
完成前两项修改后,C#版本的运行耗时就会降到和Go版本同一水平,不会再出现百倍差距,使用源生成器优化后性能还能再提升10%~20%。
内容的提问来源于stack exchange,提问作者Benj
相关产品推荐
相关产品推荐

