Go语言JSON解析届顶流:Sonic

Sonic 是字节跳动开源的一款 Go 语言 JSON 解析库,按照官方的说法:

  • Sonic 是一个速度奇快的 JSON 序列化/反序列化库,由 JIT (即时编译)和 SIMD (单指令流多数据流)加速。
  • 对于所有大小的 json 和所有使用场景, Sonic 表现均为最佳。

自 2021 年 7 月份发布以来,Sonic 已被抖音、今日头条等业务采用,累计为字节跳动节省了数十万 CPU 核(省了好多钱啊~)。

下面我们来看 Sonic 相比其他 JSON 解析库优势有多大,以及常见的使用举例。

Sonic 研发背景

  • Go 本身自带标准 JSON 库:encoding/json,另外还有很多优秀的第三方库,比如:Json-iterator、Easyjson、Gjson、Sjson 等,其中 Json-iterator 最受欢迎(12.3+k Star)。
  • 字节根据样本 JSON 的 key 数量和深度分为三个量级:
    • 小(small):400B,11 key,深度 3层;
    • 中(medium):110KB,300+ key,深度 4 层(实际业务数据,其中有大量的嵌套 JSON string);
    • 大(large):550KB,10000+ key,深度 6 层。

测试结果如下:图片结果显示:目前这些 JSON 库均无法在各场景下都保持最优性能,即使是当前使用最广泛的第三方库 Json-iterator,在泛型编解码、大数据量级场景下的性能也满足不了字节的需求。

JSON 库的基准编解码性能固然重要,但是对不同场景的最优匹配更关键 —— 于是字节走上了自研 JSON 库的道路。

Sonic 性能测试

以下是字节根据上面的不同场景进行的测试结果:

图片
小数据(400B,11 个 key,深度 3 层)
图片
中数据(110KB,300+key,深度 4 层)
图片
大数据(500KB),10000+key深度 6 层

可以看到 Sonic 在大部分场景下都有明显的优势:

  • 平均编码性能较 Json-iterator 提升 240%,平均解码性能较 Json-iterator 提升 110%;
  • 单 key 修改能力较 Sjson 提升 75%。
  • 在生产环境中,Sonic 中也验证了良好的收益,服务高峰期占用核数减少将近 ⅓:
图片
字节某服务在 *Sonic* 上线前后的 CPU 占用(核数)对比

通过上面的测试比较结果,我们很难不想去使用 Sonic 去优化我们的业务,为我们自己的系统提效添砖加瓦~

Sonic 常见使用场景

解析为 map 类型

使用 Sonic 将 JSON 数据解析为 map 类型的方法:

  • 只需要调用 Sonic 的 Unmarshal 函数,并将 JSON 数据和一个空的 map 作为参数传递即可。

例如:

package main

import (
 "github.com/bytedance/sonic"
 "fmt"
)

func main() {
    var jsonStr = `{"name": "Harper", "age": 18}`
    var data map[string]any
    
    err := sonic.Unmarshal([]byte(jsonStr), &data)
    if err != nil {
        fmt.Println("解析失败:", err)
    }
    
    fmt.Println(data["name"], data["age"])
}
图片
运行结果

解析为结构体类型

使用 Sonic 将 JSON 数据解析为结构体类型的方法:

  • 只需要定义一个结构体,用于存储解析结果,并将 JSON 数据和结构体作为参数传递给 Unmarshal 函数即可。

例如:

package main

import (
 "github.com/bytedance/sonic"
 "fmt"
)

func main() {
    type Person struct {
        Name string `json:"name"`
        Age int `json:"age"`
    }

    var jsonStr = `{"name": "Harper", "age": 20}`
    var person Person

    err := sonic.Unmarshal([]byte(jsonStr), &person)
    if err != nil {
        fmt.Println("解析失败:", err)
    }

    fmt.Println(person.Name, person.Age)
}
图片
运行结果

解析嵌套 JSON 数据

Sonic 可以解析嵌套的 JSON 数据,其实跟上面的「解析为结构体类型」原因一样,例如:

package main

import (
 "github.com/bytedance/sonic"
 "fmt"
)

func main() {
    var jsonStr = `{"name": "Harper", "age": 30, "address": {"city": "HaiDian Beijing", "country": "China"}}`

    type Address struct {
        City string `json:"city"`
        Country string `json:"country"`
    }

    type Person struct {
        Name string `json:"name"`
        Age int `json:"age"`
        Address Address `json:"address"`
    }

    var person Person
    err := sonic.Unmarshal([]byte(jsonStr), &person)
    if err != nil {
        fmt.Println("解析失败:", err)
    }
    
    fmt.Println(person.Name, person.Age, person.Address.City, person.Address.Country)
}
图片
运行结果

解析数组类型

Sonic 还可以解析数组类型的 JSON 数据,例如:

package main

import (
 "github.com/bytedance/sonic"
 "fmt"
)

func main() {
    var jsonStr = `[{"name": "Harper", "age": 33}, {"name": "Bella", "age": 34}]`

    type Person struct {
        Name string `json:"name"`
        Age int `json:"age"`
    }

    var persons []Person
    err := sonic.Unmarshal([]byte(jsonStr), &persons)
    if err != nil {
        fmt.Println("解析失败:", err)
    }
    
    for _, person := range persons {
        fmt.Println(person.Name, person.Age)
    }
}
图片
运行结果

小结

Sonic 是一款高性能的 JSON 解析库,它提供了丰富的 JSON 解析 API,包括解析为 map、结构体、嵌套 JSON 数据和数组类型等,涵盖了我们工作中几乎所有的应用场景。

特别是对于大数据解析的场景,如果我们系统遇到了 JSON 数据解析的瓶颈,不妨试试 Sonic,看看效果,毕竟替换很方便,跟 Json-iterator 类似,平替成本很低。

THE END