高性能 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+)。 - 手写 Span:
Utf8JsonReader/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+)后用常规重载。
⚠️ 不要把
options与context/typeinfo混用到错误重载上。
C) 手写 Span(Reader/Writer)
Utf8JsonReader/Utf8JsonWriter + IBufferWriter<byte> 直读直写 UTF-8;端点用 HttpContext.Request.BodyReader(PipeReader)循环读取(或采用本文默认的“收齐再解析”零拷贝方案),更稳更快。🔥
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 利用
指标权重(示例)
工具
- 微基准:BenchmarkDotNet(
[MemoryDiagnoser];可选线程/硬件计数器) - 端到端:
wrk/wrk2(吞吐+延迟分布)、hey(summary 含直方图/百分位,支持 CSV)
4. 工程要点(把分配与拷贝压到最低)🛠️
- Reader/Writer 直读直写:数值/布尔/枚举走
GetInt32()/GetDecimal()/GetBoolean()与WriteNumber()/WriteBoolean(),避免string中转。 - 属性名匹配:优先
ValueTextEquals("id")(自动反转义);不要用ValueSpan直接比较文本。 - Buffer 复用:写出侧采用
Utf8JsonWriter(IBufferWriter<byte>, …);需要池化可用ArrayPoolBufferWriter<T>。 - BodyReader:端点读取使用
BodyReader.ReadAsync()循环(或收齐再解析),避免MemoryStream中转。 - 编码/转义:
UnsafeRelaxedJsonEscaping不转义<,>,&等 HTML 敏感字符,仅在非 HTML/JS 注入语境使用。 - 多态:.NET 7+ 推荐
JsonPolymorphic/JsonDerivedType标注层次;SG 与运行时均可识别。 - AOT/Trim:Trim/NativeAOT 下可能禁止反射序列化;需用 SG 或把
TypeInfoResolver配好。 - Options 复用:
JsonSerializerOptions建议静态复用(线程安全),避免频繁 new。
4.1 零拷贝数据流🔬
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

最低0.47元/天 解锁文章

被折叠的 条评论
为什么被折叠?



