golang超高速JSON序列化与反序列化插件库sonic的使用
Golang 超高速 JSON 序列化与反序列化插件库 Sonic 的使用
Sonic 是一个由 JIT (即时编译) 和 SIMD (单指令多数据) 加速的极速 JSON 序列化和反序列化库。
要求
- Go: 1.17~1.24
- 注意:由于 issue 问题,不支持 Go1.24.0,请使用更高版本或添加构建标签
--ldflags="-checklinkname=0"
- 注意:由于 issue 问题,不支持 Go1.24.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 层)
大型 JSON (635KB, 10000+ 键, 6 层)
使用方法
序列化/反序列化
默认行为与 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.RawMessage
或 json.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.Node
或 interface{}
)。例如,你可能有以下类似 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
…),在确保安全的同时快速运行 sonicConfigStd
:标准兼容配置(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
更多关于golang超高速JSON序列化与反序列化插件库sonic的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
使用Sonic实现Go语言超高速JSON处理
Sonic是字节跳动开源的一款针对Go语言的超高性能JSON编解码库,相比标准库encoding/json
有显著的性能提升。下面详细介绍其使用方法和优势。
Sonic的核心优势
- 性能卓越:比标准库快2-10倍
- 零内存分配:大量减少GC压力
- 流式处理:支持大JSON数据流处理
- 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相比标准库有显著性能提升:
- 序列化速度:快2-5倍
- 反序列化速度:快3-10倍
- 内存分配:减少50-80%
使用建议
- 热点路径:在JSON处理成为性能瓶颈的地方使用Sonic
- 大JSON数据:优先使用流式API
- 兼容性:API与标准库相似,迁移成本低
注意事项
- Sonic不支持
json.RawMessage
- 某些极端复杂的嵌套结构可能不如标准库稳定
- 需要Go 1.16+版本
Sonic是高性能Go服务中处理JSON数据的优秀选择,特别适合微服务、API网关等高并发场景。