golang超高速JSON序列化与反序列化插件库sonic的使用

Golang 超高速 JSON 序列化与反序列化插件库 Sonic 的使用

Sonic 是一个由 JIT (即时编译) 和 SIMD (单指令多数据) 加速的极速 JSON 序列化和反序列化库。

要求

  • Go: 1.17~1.24
    • 注意:由于 issue 问题,不支持 Go1.24.0,请使用更高版本或添加构建标签 --ldflags="-checklinkname=0"
  • OS: Linux / MacOS / Windows
  • CPU: AMD64 / (ARM64, 需要 go1.20 及以上版本)

特性

  • 无需代码生成的运行时对象绑定
  • 完整的 JSON 值操作 API
  • 快,快,快!

API

完整 API 文档请参考官方文档。

基准测试

对于所有大小的 JSON 和所有使用场景Sonic 表现最佳

中型 JSON (13KB, 300+ 键, 6 层)

goversion: 1.17.1
goos: darwin
goarch: amd64
cpu: Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
BenchmarkEncoder_Generic_Sonic-16                      32393 ns/op         402.40 MB/s       11965 B/op          4 allocs/op
BenchmarkEncoder_Generic_Sonic_Fast-16                 21668 ns/op         601.57 MB/s       10940 B/op          4 allocs/op
BenchmarkEncoder_Generic_JsonIter-16                   42168 ns/op         309.12 MB/s       14345 B/op        115 allocs/op
BenchmarkEncoder_Generic_GoJson-16                     65189 ns/op         199.96 MB/s       23261 B/op         16 allocs/op
BenchmarkEncoder_Generic_StdLib-16                    106322 ns/op         122.60 MB/s       49136 B/op        789 allocs/op
BenchmarkEncoder_Binding_Sonic-16                       6269 ns/op        2079.26 MB/s       14173 B/op          4 allocs/op
BenchmarkEncoder_Binding_Sonic_Fast-16                  5281 ns/op        2468.16 MB/s       12322 B/op          4 allocs/op
BenchmarkEncoder_Binding_JsonIter-16                   20056 ns/op         649.93 MB/s        9488 B/op          2 allocs/op
BenchmarkEncoder_Binding_GoJson-16                      8311 ns/op        1568.32 MB/s        9481 B/op          1 allocs/op
BenchmarkEncoder_Binding_StdLib-16                     16448 ns/op         792.52 MB/s        9479 B/op          1 allocs/op

小型 JSON (400B, 11 键, 3 层)

small benchmarks

大型 JSON (635KB, 10000+ 键, 6 层)

large benchmarks

使用方法

序列化/反序列化

默认行为与 encoding/json 基本一致,除了 HTML 转义形式(见 Escape HTML)和 SortKeys 特性(可选支持见 Sort Keys),这些不符合 RFC8259

import "github.com/bytedance/sonic"

var data YourSchema
// 序列化
output, err := sonic.Marshal(&data)
// 反序列化
err := sonic.Unmarshal(output, &data)

流式 IO

Sonic 支持从 io.Reader 解码 JSON 或将对象编码到 io.Writer,旨在处理多个值并减少内存消耗。

  • 编码器示例:
var o1 = map[string]interface{}{
    "a": "b",
}
var o2 = 1
var w = bytes.NewBuffer(nil)
var enc = sonic.ConfigDefault.NewEncoder(w)
enc.Encode(o1)
enc.Encode(o2)
fmt.Println(w.String())
// 输出:
// {"a":"b"}
// 1
  • 解码器示例:
var o =  map[string]interface{}{}
var r = strings.NewReader(`{"a":"b"}{"1":"2"}`)
var dec = sonic.ConfigDefault.NewDecoder(r)
dec.Decode(&o)
dec.Decode(&o)
fmt.Printf("%+v", o)
// 输出:
// map[1:2 a:b]

使用 Number/Int64

import "github.com/bytedance/sonic/decoder"

var input = `1`
var data interface{}

// 默认 float64
dc := decoder.NewDecoder(input)
dc.Decode(&data) // data == float64(1)
// 使用 json.Number
dc = decoder.NewDecoder(input)
dc.UseNumber()
dc.Decode(&data) // data == json.Number("1")
// 使用 int64
dc = decoder.NewDecoder(input)
dc.UseInt64()
dc.Decode(&data) // data == int64(1)

root, err := sonic.GetFromString(input)
// 获取 json.Number
jn := root.Number()
jm := root.InterfaceUseNumber().(json.Number) // jn == jm
// 获取 float64
fn := root.Float64()
fm := root.Interface().(float64) // jn == jm

排序键

由于排序会带来约 10% 的性能损失,Sonic 默认不启用此功能。如果你的组件依赖它工作(如 zstd),可以这样使用:

import "github.com/bytedance/sonic"
import "github.com/bytedance/sonic/encoder"

// 仅绑定 map
m := map[string]interface{}{}
v, err := encoder.Encode(m, encoder.SortMapKeys)

// 或者在序列化前使用 ast.Node.SortKeys()
var root := sonic.Get(JSON)
err := root.SortKeys()

转义 HTML

由于约 15% 的性能损失,Sonic 默认不启用此功能。你可以使用 encoder.EscapeHTML 选项开启此功能(与 encoding/json.HTMLEscape 对齐)。

import "github.com/bytedance/sonic"

v := map[string]string{"&&":"<>"}
ret, err := Encode(v, EscapeHTML) // ret == `{"\u0026\u0026":{"X":"\u003c\u003e"}}`

紧凑格式

Sonic 默认将原始对象(struct/map…)编码为紧凑格式的 JSON,除了序列化 json.RawMessagejson.Marshaler:Sonic 确保验证它们的输出 JSON 但不会压缩它们以考虑性能问题。我们提供了 encoder.CompactMarshaler 选项来添加压缩过程。

打印错误

如果输入 JSON 中存在无效语法,Sonic 将返回 decoder.SyntaxError,它支持错误位置的漂亮打印。

import "github.com/bytedance/sonic"
import "github.com/bytedance/sonic/decoder"

var data interface{}
err := sonic.UnmarshalString("[[[}]]", &data)
if err != nil {
    /* 默认单行 */
    println(e.Error()) // "Syntax error at index 3: invalid char\n\n\t[[[}]]\n\t...^..\n"
    /* 漂亮打印 */
    if e, ok := err.(decoder.SyntaxError); ok {
        /*Syntax error at index 3: invalid char

            [[[}]]
            ...^..
        */
        print(e.Description())
    } else if me, ok := err.(*decoder.MismatchTypeError); ok {
        // decoder.MismatchTypeError 是 Sonic v1.6.0 新增的
        print(me.Description())
    }
}

类型不匹配 [Sonic v1.6.0]

如果给定键的值类型不匹配,Sonic 将报告 decoder.MismatchTypeError(如果有多个,报告最后一个),但仍会跳过错误的值并继续解码下一个 JSON。

import "github.com/bytedance/sonic"
import "github.com/bytedance/sonic/decoder"

var data = struct{
    A int
    B int
}{}
err := UnmarshalString(`{"A":"1","B":1}`, &data)
println(err.Error())    // Mismatch type int with value string "at index 5: mismatched type with value\n\n\t{\"A\":\"1\",\"B\":1}\n\t.....^.........\n"
fmt.Printf("%+v", data) // {A:0 B:1}

Ast.Node

Sonic/ast.Node 是一个完全自包含的 JSON AST。它实现了序列化和反序列化,并提供了强大的 API 来获取和修改通用数据。

获取/索引

通过给定路径搜索部分 JSON,路径必须是非负整数或字符串,或 nil

import "github.com/bytedance/sonic"

input := []byte(`{"key1":[{},{"key2":{"key3":[1,2,3]}}]}`)

// 无路径,返回整个 json
root, err := sonic.Get(input)
raw := root.Raw() // == string(input)

// 多路径
root, err := sonic.Get(input, "key1", 1, "key2")
sub := root.Get("key3").Index(2).Int64() // == 3

提示:由于 Index() 使用偏移量定位数据,比 Get() 这样的扫描方式快得多,我们建议你尽可能使用它。Sonic 还提供了另一个 API IndexOrGet() 来同时使用偏移量并确保键匹配。

搜索选项

Searcher 提供了一些选项来满足不同需求:

opts := ast.SearchOption{ CopyReturn: true ... }
val, err := sonic.GetWithOptions(JSON, opts, "key")
  • CopyReturn:指示搜索器复制结果 JSON 字符串而不是从输入中引用。如果你缓存结果以供重用,这有助于减少内存使用
  • ConcurentRead:由于 ast.Node 使用 Lazy-Load 设计,默认不支持并发读取。如果你想并发读取它,请指定此选项
  • ValidateJSON:指示搜索器验证整个 JSON。此选项默认启用,会稍微减慢搜索速度

设置/取消设置

通过 Set()/Unset() 修改 json 内容

import "github.com/bytedance/sonic"

// 设置
exist, err := root.Set("key4", NewBool(true)) // exist == false
alias1 := root.Get("key4")
println(alias1.Valid()) // true
alias2 := root.Index(1)
println(alias1 == alias2) // true

// 取消设置
exist, err := root.UnsetByIndex(1) // exist == true
println(root.Get("key4").Check()) // "value not exist"

序列化

要将 ast.Node 编码为 json,使用 MarshalJson()json.Marshal()(必须传递节点的指针)

import (
    "encoding/json"
    "github.com/bytedance/sonic"
)

buf, err := root.MarshalJson()
println(string(buf))                // {"key1":[{},{"key2":{"key3":[1,2,3]}}]}
exp, err := json.Marshal(&root)     // 警告:使用指针
println(string(buf) == string(exp)) // true

API

  • 验证:Check(), Error(), Valid(), Exist()
  • 搜索:Index(), Get(), IndexPair(), IndexOrGet(), GetByPath()
  • Go 类型转换:Int64(), Float64(), String(), Number(), Bool(), Map[UseNumber|UseNode](), Array[UseNumber|UseNode](), Interface[UseNumber|UseNode]()
  • Go 类型包装:NewRaw(), NewNumber(), NewNull(), NewBool(), NewString(), NewObject(), NewArray()
  • 迭代:Values(), Properties(), ForEach(), SortKeys()
  • 修改:Set(), SetByIndex(), Add()

Ast.Visitor

Sonic 提供了一个高级 API,用于将 JSON 完全解析为非标准类型(既不是 struct 也不是 map[string]interface{}),而无需使用任何中间表示(ast.Nodeinterface{})。例如,你可能有以下类似 interface{} 但实际上不是 interface{} 的类型:

type UserNode interface {}

// 以下类型实现了 UserNode 接口
type (
    UserNull    struct{}
    UserBool    struct{ Value bool }
    UserInt64   struct{ Value int64 }
    UserFloat64 struct{ Value float64 }
    UserString  struct{ Value string }
    UserObject  struct{ Value map[string]UserNode }
    UserArray   struct{ Value []UserNode }
)

Sonic 提供了以下 API 来返回 JSON AST 的前序遍历ast.Visitor 是一个 SAX 风格的接口,用于在一些 C++ JSON 库中。你应该自己实现 ast.Visitor 并将其传递给 ast.Preorder() 方法。在你的访问者中,你可以创建自定义类型来表示 JSON 值。你的访问者中可能有一个 O(n) 空间容器(如堆栈)来记录对象/数组层次结构。

func Preorder(str string, visitor Visitor, opts *VisitorOptions) error

type Visitor interface {
    OnNull() error
    OnBool(v bool) error
    OnString(v string) error
    OnInt64(v int64, n json.Number) error
    OnFloat64(v float64, n json.Number) error
    OnObjectBegin(capacity int) error
    OnObjectKey(key string) error
    OnObjectEnd() error
    OnArrayBegin(capacity int) error
    OnArrayEnd() error
}

兼容性

对于希望使用 sonic 满足不同场景的开发人员,我们提供了一些集成配置作为 sonic.API

  • ConfigDefault:sonic 的默认配置(EscapeHTML=false,SortKeys=false…),在确保安全的同时快速运行 sonic
  • ConfigStd:标准兼容配置(EscapeHTML=true,SortKeys=true…)
  • ConfigFastest:最快的配置(NoQuoteTextMarshaler=true)尽可能快地运行 sonic

Sonic 确保支持所有环境,由于开发高性能代码的困难。在不支持 sonic 的环境中,实现将回退到 encoding/json。因此以下配置都将等于 ConfigStd

提示

预加载

由于 Sonic 使用 golang-asm 作为 JIT 汇编器,它不太适合运行时编译,巨大模式的首次运行可能导致请求超时甚至进程 OOM。为了更好的稳定性,我们建议在 Marshal()/Unmarshal() 之前对巨大模式或紧凑内存应用程序使用 Pretouch()

import (
    "reflect"
    "github.com/bytedance/sonic"
    "github.com/bytedance/sonic/option"
)

func init() {
    var v HugeStruct

    // 对于大多数大型类型(嵌套深度 <= option.DefaultMaxInlineDepth)
    err := sonic.Pretouch(reflect.TypeOf(v))

    // 更多 CompileOption...
    err := sonic.Pretouch(reflect.TypeOf(v),
        // 如果类型嵌套太深(嵌套深度 > option.DefaultMaxInlineDepth),
        // 你可以在 Pretouch 中设置编译递归循环以获得更好的 JIT 稳定性
        option.WithCompileRecursiveDepth(loop),
        // 对于大型嵌套结构,尝试设置较小的深度以减少编译时间
        option.WithCompileMaxInlineDepth(depth),
    )
}

复制字符串

当解码没有任何转义字符的字符串值时,sonic 从原始 JSON 缓冲区引用它们,而不是分配新缓冲区来复制。这对 CPU 性能有很大帮助,但只要解码对象正在使用,整个 JSON 缓冲区就可能留在内存中。在实践中,我们发现引用 JSON 缓冲区引入的额外内存通常是解码对象的 20% ~ 80%。一旦应用程序长时间持有这些对象(例如,缓存解码对象以供重用),服务器上的使用内存可能会上升。

  • Config.CopyString/decoder.CopyString():我们为 Decode() / Unmarshal() 用户提供了不引用 JSON 缓冲区的选项,这可能会在一定程度上导致 CPU 性能下降
  • GetFromStringNoCopy():为了内存安全,sonic.Get() / sonic.GetFromString() 现在会复制返回的 JSON。如果你想更快地获取 json 并且不关心内存使用情况,可以使用 GetFromStringNoCopy() 直接返回从源引用的 JSON

传递字符串还是 []byte?

为了与 encoding/json 对齐,我们提供了传递 []byte 作为参数的 API,但考虑到安全性,同时进行了字符串到字节的复制,当原始 JSON 很大时可能会丢失性能。因此,你可以使用 UnmarshalString()GetFromString() 来传递字符串,只要你的原始数据是字符串或 nocopy-cast 对你的 []byte 是安全的。我们还提供了 API MarshalString() 用于编码 JSON []byte 的便捷 nocopy-cast,这是安全的,因为 sonic 的输出字节总是重复且唯一的。

加速 encoding.TextMarshaler

为了确保数据安全,sonic.Encoder 默认对来自 encoding.TextMarshaler 接口的字符串值进行引号和转义,如果你的大部分数据都是这种形式,可能会大大降低性能。我们提供了 encoder.NoQuoteTextMarshaler 来跳过这些操作,这意味着你必须确保它们的输出字符串按照 RFC8259 进行转义和引号。

通用数据的更好性能

完全解析的场景中,Unmarshal() 比 `Get


更多关于golang超高速JSON序列化与反序列化插件库sonic的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang超高速JSON序列化与反序列化插件库sonic的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


使用Sonic实现Go语言超高速JSON处理

Sonic是字节跳动开源的一款针对Go语言的超高性能JSON编解码库,相比标准库encoding/json有显著的性能提升。下面详细介绍其使用方法和优势。

Sonic的核心优势

  1. 性能卓越:比标准库快2-10倍
  2. 零内存分配:大量减少GC压力
  3. 流式处理:支持大JSON数据流处理
  4. API兼容:与标准库API高度兼容

安装Sonic

go get github.com/bytedance/sonic

基本使用示例

1. 序列化(Marshal)

package main

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

type User struct {
	Name  string `json:"name"`
	Age   int    `json:"age"`
	Email string `json:"email,omitempty"`
}

func main() {
	user := User{
		Name: "张三",
		Age:  28,
	}

	// 标准序列化
	jsonBytes, err := sonic.Marshal(&user)
	if err != nil {
		panic(err)
	}
	fmt.Println(string(jsonBytes)) // {"name":"张三","age":28}

	// 带缩进的格式化输出
	jsonIndent, err := sonic.MarshalIndent(&user, "", "  ")
	if err != nil {
		panic(err)
	}
	fmt.Println(string(jsonIndent))
	/*
	  {
	    "name": "张三",
	    "age": 28
	  }
	*/
}

2. 反序列化(Unmarshal)

func main() {
	jsonStr := `{"name":"李四","age":30,"email":"lisi@example.com"}`

	var user User
	err := sonic.Unmarshal([]byte(jsonStr), &user)
	if err != nil {
		panic(err)
	}

	fmt.Printf("%+v\n", user) 
	// {Name:李四 Age:30 Email:lisi@example.com}
}

高级特性

1. 流式处理

对于大JSON数据,可以使用流式API:

import "github.com/bytedance/sonic/decoder"

func streamDecode() {
	// 模拟大JSON数据流
	bigJSON := `{"name":"王五","age":35}{"name":"赵六","age":40}`

	stream := decoder.NewStreamDecoder(strings.NewReader(bigJSON))
	for {
		var user User
		if err := stream.Decode(&user); err != nil {
			if err == io.EOF {
				break
			}
			panic(err)
		}
		fmt.Printf("流式处理: %+v\n", user)
	}
	/*
	  流式处理: {Name:王五 Age:35 Email:}
	  流式处理: {Name:赵六 Age:40 Email:}
	*/
}

2. 性能优化选项

func main() {
	user := User{Name: "张三", Age: 28}

	// 使用NoQuoteTextMarshaler选项提升字符串处理性能
	jsonBytes, err := sonic.Config{
		NoQuoteTextMarshaler: true,
	}.Marshal(&user)
	
	if err != nil {
		panic(err)
	}
	fmt.Println(string(jsonBytes))
}

性能对比

Sonic相比标准库有显著性能提升:

  1. 序列化速度:快2-5倍
  2. 反序列化速度:快3-10倍
  3. 内存分配:减少50-80%

使用建议

  1. 热点路径:在JSON处理成为性能瓶颈的地方使用Sonic
  2. 大JSON数据:优先使用流式API
  3. 兼容性:API与标准库相似,迁移成本低

注意事项

  1. Sonic不支持json.RawMessage
  2. 某些极端复杂的嵌套结构可能不如标准库稳定
  3. 需要Go 1.16+版本

Sonic是高性能Go服务中处理JSON数据的优秀选择,特别适合微服务、API网关等高并发场景。

回到顶部