高性能 JSON:System.Text.Json Source Generator vs 手写 Span(Utf8JsonReader/Writer)

高性能 JSON:System.Text.Json Source Generator vs 手写 Span(Utf8JsonReader/Writer)⚡️



1. 为什么比较 & 本文目标 🎯

  • 反射默认(JsonSerializer + Options):易用灵活,但运行期反射与元数据访问有开销;在 Trim/NativeAOT 下可能禁用反射序列化,需要 Source Generator(SG)或 TypeInfoResolver 才能工作。
  • Source Generator(SG):编译期生成类型元数据/专用代码,冷启动更快、AOT/Trim 友好;调用入口三选一:传 JsonTypeInfo<T>、传 JsonSerializerContext、或设置 JsonSerializerOptions.TypeInfoResolver(.NET 7+)。
  • 手写 SpanUtf8JsonReader/Writer 直读直写 UTF-8,配 IBufferWriter<byte> 与管道实现低分配;适合顶级热路径、大批量与严格内存预算场景。

目标:在冷/热路径、S/M/B 对象规模与批量场景下,给出可复现的性能结论与可运维的选型标准。🧰


2. 参赛选手与实现基线 🧑‍💻

A) 反射默认(Baseline)

使用 JsonSerializer.Serialize/Deserialize(obj, options)全局复用一个 JsonSerializerOptions(线程安全),避免频繁创建导致缓存失效与性能回退。♻️

B) Source Generator(生成器)

定义 partial class XxxJsonContext : JsonSerializerContext,在类上用 [JsonSerializable(typeof(...))] 标注需要的类型。调用时三选一:

  • JsonTypeInfo<T>;或
  • JsonSerializerContext;或
  • XxxJsonContext.Default 赋给 JsonSerializerOptions.TypeInfoResolver(.NET 7+)后用常规重载。

⚠️ 不要把 optionscontext/typeinfo 混用到错误重载上。

C) 手写 Span(Reader/Writer)

Utf8JsonReader/Utf8JsonWriter + IBufferWriter<byte> 直读直写 UTF-8;端点用 HttpContext.Request.BodyReaderPipeReader循环读取(或采用本文默认的“收齐再解析”零拷贝方案),更稳更快。🔥

2.1 选型一图流 🧭

在这里插入图片描述


3. 测试样本与评测矩阵 🧪

数据模型

  • S(小对象):10–15 属性(数值/字符串/枚举/DateTimeOffset
  • M(中对象):3 层嵌套 + 数组(50–100 属性)
  • B(批量)List<S> 1k/10k(模拟埋点/日志批量)

工况维度

  • 冷启动 vs 热路径;序列化/反序列化分别测
  • 指标:吞吐(MB/s 或 req/s)、延迟分位(P50/P95/P99)、分配(B/op)、GC 次数、CPU 利用

指标权重(示例)

40% 35% 20% 5% 指标权重(可按团队目标调整) 吞吐 延迟 (P95/P99) 分配 (B/op) 稳定性/GC

工具

  • 微基准:BenchmarkDotNet([MemoryDiagnoser];可选线程/硬件计数器)
  • 端到端wrk/wrk2(吞吐+延迟分布)、hey(summary 含直方图/百分位,支持 CSV)

4. 工程要点(把分配与拷贝压到最低)🛠️

  1. Reader/Writer 直读直写:数值/布尔/枚举走 GetInt32()/GetDecimal()/GetBoolean()WriteNumber()/WriteBoolean(),避免 string 中转。
  2. 属性名匹配:优先 ValueTextEquals("id")(自动反转义);不要ValueSpan 直接比较文本。
  3. Buffer 复用:写出侧采用 Utf8JsonWriter(IBufferWriter<byte>, …);需要池化可用 ArrayPoolBufferWriter<T>
  4. BodyReader:端点读取使用 BodyReader.ReadAsync() 循环(或收齐再解析),避免 MemoryStream 中转。
  5. 编码/转义UnsafeRelaxedJsonEscaping 不转义 <, >, &HTML 敏感字符,仅在非 HTML/JS 注入语境使用。
  6. 多态:.NET 7+ 推荐 JsonPolymorphic/JsonDerivedType 标注层次;SG 与运行时均可识别。
  7. AOT/Trim:Trim/NativeAOT 下可能禁止反射序列化;需用 SG 或把 TypeInfoResolver 配好。
  8. Options 复用JsonSerializerOptions 建议静态复用(线程安全),避免频繁 new。

4.1 零拷贝数据流🔬

Kestrel
请求体: UTF-8 字节
BodyReader(PipeReader)
isCompleted?
ReadOnlySequence (全量)
Utf8JsonReader(finalBuffer)
OrderMan.Read
Utf8JsonWriter(BodyWriter)
响应: UTF-8 JSON

5. 可复现仓库骨架(.NET 8/9)🧱

JsonPerfPlayground/
├─ src/
│  ├─ JsonPerf.Codecs/      # 模型 & 三路实现(统一 IJsonCodec<T>)
│  ├─ JsonPerf.Bench/       # BenchmarkDotNet 基准
│  └─ JsonPerf.Api/         # Minimal API(端到端对比)
├─ scripts/
│  ├─ wrk.sh                # wrk/wrk2 压测脚本(已修正 BODY 传递)
│  └─ hey.sh                # hey 压测脚本
└─ Directory.Build.props    # TreatWarningsAsErrors, LangVersion 等

5.1 统一接口与模型(JsonPerf.Codecs)🧩

// IJsonCodec.cs
public interface IJsonCodec<T>
{
   
   
    byte[] Serialize(T value);
    T Deserialize(ReadOnlySpan<byte> utf8Json);
}

// Models.cs(S/M/B 示例)
public enum OrderState : byte {
   
    New, Paid, Shipped, Cancelled }

public sealed class OrderItem
{
   
   
    public int Id {
   
    get; set; }
    public string Sku {
   
    get; set; } = "";
    public int Qty {
   
    get; set; }
    public decimal Price {
   
    get; set; }
}

public sealed class Order
{
   
   
    public int Id {
   
    get; set; }
    public string Customer {
   
    get; set; } = "";
    public DateTimeOffset CreatedAt {
   
    get; set; }
    public OrderState State {
   
    get; set; }
    public List<OrderItem> Items {
   
    get; set; } = new();
    public Dictionary<string, string> Tags {
   
    get; set; } = new
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Kookoos

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值