avatar🌌
TangentTangent Blog

技术博客 | Go & Kubernetes & Linux

技术博客 | Go & Kubernetes & Linux

Go JSON 库性能对比:Std JSON V1/V2 vs Sonic

所有基准测试运行在 Go 1.25.1 环境下,数据规模从 1MB 到 100MB 不等。

1. 原生标准库:Std JSON (V1) ​

原生 Std JSON 是指 encoding/json 包在默认设置下的表现。它以稳定性和兼容性著称,但在处理大量数据时,尤其是在反序列化(Unmarshal)过程中,其性能瓶颈较为明显。

示例代码:反序列化(Unmarshal) ​

我们使用标准库的 json.Unmarshal 对预加载的 JSON 字节数组进行操作,并报告内存分配次数 (b.ReportAllocs())。

go
func BenchmarkStdJSONUnmarshal1MB(b *testing.B) {
    b.ReportAllocs()
    for b.Loop() {
        var result []TestData
        err := json.Unmarshal(jsonData1MB, &result) // 使用 encoding/json
        if err != nil { b.Fatal(err) }
    }
}

性能分析(基于 100MB 数据) ​

操作类型平均吞吐量 (MB/s)平均分配次数 (allocs/op)结果来源
Marshal (序列化)约 843 – 878 MB/s2 – 7
Unmarshal (反序列化)约 157 – 172 MB/s约 699,590 次

关键洞察:

  • 序列化性能尚可: V1 版本的序列化性能较高,平均能达到 800 MB/s 以上。
  • 反序列化是瓶颈: Std JSON V1 在反序列化时的吞吐量非常低,仅为序列化性能的约 20%。
  • 高 GC 压力: 最显著的问题是内存分配。在反序列化 100MB 数据时,它产生了近 70 万次的内存分配,这对 Go 运行时的垃圾回收(GC)系统造成了巨大的压力,这是导致其性能低下的主要原因。

启动指令 (Std JSON V1) ​

运行所有名称包含 StdJSON 的基准测试:

shell
go test -bench=StdJSON -count=10 -benchmem ...

task benchmark-std

2. JSON V2 (Go 1.25 实验性) ​

JSON V2 是 Go 1.25 版本中引入的实验性实现,旨在优化标准库的性能。它的调用接口与 V1 完全相同,但通过设置 GOEXPERIMENT jsonv2 旗标来启用。

示例代码:反序列化(Unmarshal) ​

虽然底层实现不同,但用户代码保持不变,仍使用 encoding/json 包:

go
// 代码与 V1 相同,但 GOEXPERIMENT 旗标激活了 V2 逻辑
err := json.Unmarshal(jsonData1MB, &result)

性能分析(基于 100MB 数据) ​

操作类型平均吞吐量 (MB/s)平均分配次数 (allocs/op)结果来源
Marshal (序列化)约 810 – 853 MB/s3 – 9
Unmarshal (反序列化)约 325 – 348 MB/s约 653,504 次

关键洞察:

  • 序列化提升不明显: V2 在序列化 (Marshal) 方面的吞吐量与 V1 基本持平。
  • 反序列化显著提升: V2 在反序列化 (Unmarshal) 方面表现出色,吞吐量比 V1 提高了约一倍 (157 MB/s 提升到约 347 MB/s)。
  • 内存分配问题仍存在: 尽管速度加快,但 V2 在内存分配次数上(约 65 万次)相比 V1 减少有限,这意味着其仍然对 GC 造成较高的压力。

启动指令 (JSON V2) ​

必须设置 GOEXPERIMENT jsonv2 环境变量来启用 V2 实现:

shell
set -x GOEXPERIMENT jsonv2
go test -bench=StdJSO -count=10 -benchmem ...

task benchmark-jsonv2

3. 第三方库:Sonic ​

Sonic (github.com/bytedance/sonic) 是一个高度优化的 JSON 库,常用于对性能有极高要求的场景。测试中使用了其最快配置 (sonic.ConfigFastest)。

示例代码:反序列化(Unmarshal - Fastest 配置) ​

Sonic 提供了专用的 API,可以直接使用 sonic.Unmarshal 或预配置的 sonicFastest 实例进行操作:

go
// 预设 fastest 配置实例
var sonicFastest = sonic.ConfigFastest

func BenchmarkSonicFastestUnmarshal1MB(b *testing.B) {
    b.ReportAllocs()
    for b.Loop() {
        var result []TestData
        // 使用 ConfigFastest 实例进行反序列化
        err := sonicFastest.Unmarshal(jsonData1MB, &result) 
        if err != nil { b.Fatal(err) }
    }
}

性能分析(基于 100MB 数据) ​

操作类型平均吞吐量 (MB/s)平均分配次数 (allocs/op)结果来源
Marshal (序列化)约 952 – 987 MB/s33 – 34 次
Unmarshal (反序列化)约 1090 – 1107 MB/s约 50,250 次

关键洞察:

  • 序列化领先: Sonic 的序列化性能优于 V1 和 V2,可达近 1000 MB/s。
  • 反序列化优势巨大: Sonic 在反序列化方面展现出压倒性优势,吞吐量超过 1 GB/s,是 V1 的 6 倍,V2 的 3 倍。
  • 内存效率革命性提升: Sonic 在反序列化 100MB 数据时,内存分配次数仅为 50,250 次,这比 V1/V2 的近 70 万次分配减少了一个数量级以上,极大地降低了 GC 压力。

启动指令 (Sonic) ​

运行所有名称包含 Sonic 的基准测试:

shell
go test -bench=Sonic -count=10 -benchmem ...

task benchmark-sonic

4. 性能总结与选型分析 ​

下表对比了三种 JSON 处理机制在处理大 JSON 数据(100MB)时的关键性能指标:

实现Unmarshal 吞吐量 (MB/s)Marshal 吞吐量 (MB/s)Unmarshal Allocations (allocs/op)GC 压力依赖性
Std JSON (V1)157 – 172843 – 878高 (≈ 700k)极高无(标准库)
JSON V2 (Go 1.25)325 – 348810 – 853高 (≈ 650k)高无(标准库,需实验旗标)
Sonic (Fastest)1090 – 1107952 – 987低 (≈ 50k)低外部库

选型策略分析 ​

在选择 Go 项目中的 JSON 库时,应根据项目的性能需求、依赖限制和 Go 版本进行权衡:

  1. 首选 Sonic (极致性能需求)

    • 适用场景: 对吞吐量有极高要求的高并发服务(如 API Gateway、数据处理管道),以及对延迟敏感且数据量大的场景。
    • 优势: 无论序列化还是反序列化,性能都是最高的。最重要的是,它极大地优化了内存分配(反序列化分配次数比原生库少一个数量级),显著减轻了 GC 负担。
    • 代价: 引入了外部依赖。
  2. 次选 JSON V2 (标准库性能提升)

    • 适用场景: 运行在 Go 1.25 及更高版本,需要维持标准库的兼容性,同时希望在不引入外部依赖的情况下获得显著性能提升。
    • 优势: 反序列化速度是 V1 的两倍,但仍然是标准库的一部分,无需管理额外的依赖。
    • 代价: 必须启用实验性旗标 (GOEXPERIMENT jsonv2),且其反序列化过程中的内存分配次数仍然很高(GC 压力大)。
  3. 基础选择 Std JSON V1 (兼容性和稳定性)

    • 适用场景: 对性能要求不高、需要最大限度保证跨版本兼容性和代码稳定性的基础服务。
    • 优势: 标准库默认行为,无需任何配置,最为稳定和通用。
    • 代价: 在处理中等到大型 JSON 负载时,其反序列化性能最差,且内存分配带来的 GC 延迟最高。
Go JSON Library Performance Comparison: Std JSON V1/V2 vs Sonic
Go-Zero Concurrency Tool: Detailed Analysis of `mr` Package Functions and Task Concurrency Execution Principles
由 Valaxy v1.2.2 驱动|主题-Yunv1.2.2