大家好,我是Mandy,上一节咱们对Go中的切片数据类型进行了深度的分析,明天给大家分享一个字节跳动自研开源
的JSON数据解析包。一个速度奇快的 JSON 序列化/反序列化库,由 JIT (即时编译)和 SIMD (单指令流多数据流)减速。
sonic 是字节跳动开源的一款 Golang JSON 库,基于即时编译(Just-In-Time Compilation)与向量化编程(Single Instruction Multiple Data)技术,大幅晋升了 Go 程序的 JSON 编解码性能。同时联合 lazy-load 设计思维,它也为不同业务场景打造了一套全面高效的 API。
自研背景
Go 自身自带规范 JSON 库:encoding/json,另外还有很多优良的第三方库,比方:Json-iterator、Easyjson、Gjson、Sjson 等,其中 Json-iterator 最受欢迎(12.3+k Star)。那为什么字节跳动还会抉择自研一个JSON解析库呢?
JSON(JavaScript Object Notation) 以其简洁的语法和灵便的自描述能力,被广泛应用于各互联网业务。然而 JSON 因为实质是一种文本协定,且没有相似 Protobuf 的强制模型束缚(schema),编解码效率往往非常低下。再加上有些业务开发者对 JSON 库的不失当选型与应用,最终导致服务性能急剧劣化。
依据字节跳动生产服务的整体剖析,咱们发现 JSON 序列化和反序列化的开销意外地很高:CPU 使用率靠近 10%,其中极其状况下超过 40%。因而,JSON 库的性能是进步机器利用率的关键问题。
在字节跳动,咱们也遇到了上述问题。依据此前统计的公司 CPU 占比 TOP 50 服务的性能剖析数据,JSON 编解码开销总体靠近 10%,单个业务占比甚至超过 40%,晋升 JSON 库的性能至关重要。因而咱们对业界现有 Go JSON 库进行了一番评估测试。
首先,依据支流 JSON 库 API,咱们将它们的应用形式分为三种:
- 泛型(generic)编解码:JSON 没有对应的 schema,只能根据自描述语义将读取到的 value 解释为对应语言的运行时对象,例如:JSON object 转化为 Go map[string]interface{};
- 定型(binding)编解码:JSON 有对应的 schema,能够同时联合模型定义(Go struct)与 JSON 语法,将读取到的 value 绑定到对应的模型字段下来,同时实现数据解析与校验;
- 查找(get)& 批改(set) :指定某种规定的查找门路(个别是 key 与 index 的汇合),获取须要的那局部 JSON value 并解决。
其次,咱们依据样本 JSON 的 key 数量和深度分为三个量级:
- 小(small):400B,11 key,深度 3 层;
- 中(medium):110KB,300+ key,深度 4 层(理论业务数据,其中有大量的嵌套 JSON string);
- 大(large):550KB,10000+ key,深度 6 层。
如何应用
依赖
- Go 1.16~1.20
- Linux / MacOS / Windows(须要 Go1.17 以上)
- Amd64 架构
特色
- 运行时对象绑定,无需代码生成
- 齐备的 JSON 操作 API
- 快,更快,还要更快!
应用形式
序列化/反序列化
默认的行为基本上与 encoding/json
相一致,除了 HTML 本义模式(参见 Escape HTML) 和 SortKeys
性能(参见 Sort Keys)没有遵循 RFC8259 。
import "github.com/bytedance/sonic"var data YourSchema// Marshaloutput, err := sonic.Marshal(&data)// Unmarshalerr := sonic.Unmarshal(output, &data)
流式输入输出
Sonic 反对解码 io.Reader
中输出的 json,或将对象编码为 json 后输入至 io.Writer
,以解决多个值并缩小内存耗费。
编码器
var o1 = map[string]interface{}{ "a": "b",}var o2 = 1var w = bytes.NewBuffer(nil)var enc = sonic.ConfigDefault.NewEncoder(w)enc.Encode(o1)enc.Encode(o2)fmt.Println(w.String())// Output:// {"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)// Output:// map[1:2 a:b]
应用 Number
/ int64
import "github.com/bytedance/sonic/decoder"var input = `1`var data interface{}// default float64dc := decoder.NewDecoder(input)dc.Decode(&data) // data == float64(1)// use json.Numberdc = decoder.NewDecoder(input)dc.UseNumber()dc.Decode(&data) // data == json.Number("1")// use int64dc = decoder.NewDecoder(input)dc.UseInt64()dc.Decode(&data) // data == int64(1)root, err := sonic.GetFromString(input)// Get json.Numberjn := root.Number()jm := root.InterfaceUseNumber().(json.Number) // jn == jm// Get float64fn := root.Float64()fm := root.Interface().(float64) // jn == jm
对键排序
思考到排序带来的性能损失(约 10% ), sonic 默认不会启用这个性能。如果你的组件依赖这个行为(如 zstd) ,能够仿照上面的例子:
import "github.com/bytedance/sonic"import "github.com/bytedance/sonic/encoder"// Binding map onlym := map[string]interface{}{}v, err := encoder.Encode(m, encoder.SortMapKeys)// Or ast.Node.SortKeys() before marshalvar 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
or 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 { /* One line by default */ println(e.Error()) // "Syntax error at index 3: invalid char\n\n\t[[[}]]\n\t...^..\n" /* Pretty print */ 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 is new to 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 形象语法树库。它实现了序列化和反序列化,并提供了获取和批改通用数据的鲁棒的 API。
查找/索引
通过给定的门路搜寻 JSON 片段,门路必须为非负整数,字符串或 nil
。
import "github.com/bytedance/sonic"input := []byte(`{"key1":[{},{"key2":{"key3":[1,2,3]}}]}`)// no path, returns entire jsonroot, err := sonic.Get(input)raw := root.Raw() // == string(input)// multiple pathsroot, err := sonic.Get(input, "key1", 1, "key2")sub := root.Get("key3").Index(2).Int64() // == 3
留神:因为 Index()
应用偏移量来定位数据,比应用扫描的 Get()
要快的多,倡议尽可能的应用 Index
。 Sonic 也提供了另一个 API, IndexOrGet()
,以偏移量为根底并且也确保键的匹配。
批改
应用 Set()
/ Unset()
批改 json 的内容
import "github.com/bytedance/sonic"// Setexist, err := root.Set("key4", NewBool(true)) // exist == falsealias1 := root.Get("key4")println(alias1.Valid()) // truealias2 := root.Index(1)println(alias1 == alias2) // true// Unsetexist, err := root.UnsetByIndex(1) // exist == trueprintln(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) // WARN: use pointerprintln(string(buf) == string(exp)) // true
APIs
- 合法性检查:
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{}
,但实际上并不是:
type UserNode interface {}// the following types implement the UserNode interface.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 的值。在你的 ast.Visitor
中,可能须要有一个 O(n) 空间复杂度的容器(比如说栈)来记录 object / array 的层级。
func Preorder(str string, visitor Visitor, opts *VisitorOptions) errortype 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}
具体用法参看 ast/visitor.go,咱们还为 UserNode
实现了一个示例 ast.Visitor
,你能够在 ast/visitor_test.go 中找到它。
兼容性
因为开发高性能代码的困难性, Sonic 不保障对所有环境的反对。对于在不同环境中应用 Sonic 构建应用程序的开发者,咱们有以下倡议:
- 在 Mac M1 上开发:确保在您的计算机上安装了 Rosetta 2,并在构建时设置
GOARCH=amd64
。 Rosetta 2 能够主动将 x86 二进制文件转换为 arm64 二进制文件,并在 Mac M1 上运行 x86 应用程序。 - 在 Linux arm64 上开发:您能够装置 qemu 并应用
qemu-x86_64 -cpu max
命令来将 x86 二进制文件转换为 arm64 二进制文件。qemu能够实现与Mac M1上的Rosetta 2相似的转换成果。
对于心愿在不应用 qemu 下应用 sonic 的开发者,或者心愿解决 JSON 时与 encoding/JSON
严格保持一致的开发者,咱们在 sonic.API
中提供了一些兼容性 API
ConfigDefault
: 在反对 sonic 的环境下 sonic 的默认配置(EscapeHTML=false
,SortKeys=false
等)。行为与具备相应配置的encoding/json
统一,一些选项,如SortKeys=false
将有效。ConfigStd
: 在反对 sonic 的环境下与规范库兼容的配置(EscapeHTML=true
,SortKeys=true
等)。行为与encoding/json
统一。ConfigFastest
: 在反对 sonic 的环境下运行最快的配置(NoQuoteTextMarshaler=true
)。行为与具备相应配置的encoding/json
统一,某些选项将有效。
注意事项
预热
因为 Sonic 应用 golang-asm 作为 JIT 汇编器,这个库并不适用于运行时编译,第一次运行一个大型模式可能会导致申请超时甚至过程内存溢出。为了更好地稳定性,咱们倡议在运行大型模式或在内存无限的利用中,在应用 Marshal()/Unmarshal()
前运行 Pretouch()
。
import ( "reflect" "github.com/bytedance/sonic" "github.com/bytedance/sonic/option")func init() { var v HugeStruct // For most large types (nesting depth <= option.DefaultMaxInlineDepth) err := sonic.Pretouch(reflect.TypeOf(v)) // with more CompileOption... err := sonic.Pretouch(reflect.TypeOf(v), // If the type is too deep nesting (nesting depth > option.DefaultMaxInlineDepth), // you can set compile recursive loops in Pretouch for better stability in JIT. option.WithCompileRecursiveDepth(loop), // For a large nested struct, try to set a smaller depth to reduce compiling time. option.WithCompileMaxInlineDepth(depth), )}
拷贝字符串
当解码 没有转义字符的字符串时, sonic 会从原始的 JSON 缓冲区内援用而不是复制到新的一个缓冲区中。这对 CPU 的性能方面很有帮忙,然而可能因而在解码后对象仍在应用的时候将整个 JSON 缓冲区保留在内存中。实际中咱们发现,通过援用 JSON 缓冲区引入的额定内存通常是解码后对象的 20% 至 80% ,一旦利用长期保留这些对象(如缓存以备重用),服务器所应用的内存可能会减少。咱们提供了选项 decoder.CopyString()
供用户抉择,不援用 JSON 缓冲区。这可能在肯定水平上升高 CPU 性能。
传递字符串还是字节数组?
为了和 encoding/json
保持一致,咱们提供了传递 []byte
作为参数的 API ,但思考到安全性,字符串到字节的复制是同时进行的,这在原始 JSON 十分大时可能会导致性能损失。因而,你能够应用 UnmarshalString()
和 GetFromString()
来传递字符串,只有你的原始数据是字符串,或零拷贝类型转换对于你的字节数组是平安的。咱们也提供了 MarshalString()
的 API ,以便对编码的 JSON 字节数组进行零拷贝类型转换,因为 sonic 输入的字节始终是反复并且惟一的,所以这样是平安的。
减速 encoding.TextMarshaler
为了保证数据安全性, sonic.Encoder
默认会对来自 encoding.TextMarshaler
接口的字符串进行援用和本义,如果大部分数据都是这种模式那可能会导致很大的性能损失。咱们提供了 encoder.NoQuoteTextMarshaler
选项来跳过这些操作,但你必须保障他们的输入字符串按照 RFC8259 进行了本义和援用。
泛型的性能优化
在 齐全解析的场景下, Unmarshal()
体现得比 Get()
+Node.Interface()
更好。然而如果你只有特定 JSON 的局部模式,你能够将 Get()
和 Unmarshal()
联合应用:
import "github.com/bytedance/sonic"node, err := sonic.GetFromString(_TwitterJson, "statuses", 3, "user")var user User // your partial schema...err = sonic.UnmarshalString(node.Raw(), &user)
甚至如果你没有任何模式,能够用 ast.Node
代替 map
或 interface
作为泛型的容器:
import "github.com/bytedance/sonic"root, err := sonic.GetFromString(_TwitterJson)user := root.GetByPath("statuses", 3, "user") // === root.Get("status").Index(3).Get("user")err = user.Check()// err = user.LoadAll() // only call this when you want to use 'user' concurrently...go someFunc(user)
为什么?因为 ast.Node
应用 array
来存储其子节点:
- 在插入(反序列化)和扫描(序列化)数据时,
Array
的性能比Map
好得多; - 哈希(
map[x]
)的效率不如索引(array[x]
)高效,而ast.Node
能够在数组和对象上应用索引; - 应用
Interface()
/Map()
意味着 sonic 必须解析所有的底层值,而ast.Node
能够按需解析它们。
留神:因为 ast.Node
的惰性加载设计,其不能间接保障并发安全性,但你能够调用 Node.Load()
/ Node.LoadAll()
来实现并发平安。只管可能会带来性能损失,但仍比转换成 map
或 interface{}
更为高效。
应用 ast.Node
还是 ast.Visitor
?
对于泛型数据的解析,ast.Node
在大多数场景上应该可能满足你的需要。
然而,ast.Node
是一种针对局部解析 JSON 而设计的泛型容器,它蕴含一些非凡设计,比方惰性加载,如果你心愿像 Unmarshal()
那样间接解析整个 JSON,这些设计可能并不适合。只管 ast.Node
相较于 map
或 interface{}
来说是更好的一种泛型容器,但它毕竟也是一种两头示意,如果你的最终类型是自定义的,你还得在解析实现后将上述类型转化成你自定义的类型。
在上述场景中,如果想要有更极致的性能,ast.Visitor
会是更好的抉择。它采纳和 Unmarshal()
相似的模式解析 JSON,并且你能够间接应用你的最终类型去示意 JSON AST,而不须要通过额定的任何两头示意。
然而,ast.Visitor
并不是一个很易用的 API。你可能须要写大量的代码去实现本人的 ast.Visitor
,并且须要在解析过程中认真保护树的层级。如果你决定要应用这个 API,请先仔细阅读 ast/visitor.go 中的正文。
底层原理
在设计之初,字节研发团队做了如下几个问题的思考:
为什么 Json-iterator 比规范库快?
首先,规范库应用的基于模式(Schema)的解决机制是值得称赞的,解析器能够在扫描时提前获取元信息,从而缩短分支抉择的工夫。然而,它的原始实现没有很好地利用这个机制,而是破费了大量工夫应用反射获取模式的元信息。与此同时,json-iterator 的办法是:将构造解释为一一字段的编码和解码函数,而后将它们组装和缓存起来,最小化反射带来的性能损失。但这种办法是否一劳永逸呢?理论测试中,咱们发现随着输出的 JSON 变深、变大,json-iterator 和其余库之间的差距逐步放大——甚至最终被超过:
起因是该实现转化为大量接口封装和函数调用,导致了函数调用的性能损失:
- 调用接口波及到对
itab
的动静地址获取 - 组装的函数无奈内联,而 Golang 的函数调用性能较差(没有寄存器传参)
有没有方法防止动静组装函数的调用开销?
咱们首先思考的是相似easyjson的代码生成。然而这会带来模式依赖和便利性降落。为了实现对规范库的真正插拔式替换,咱们转向了另一种技术- JIT (即时编译)。因为编译后的编解码函数是一个集成的函数,它能够大大减少函数调用,同时保障灵活性。
为什么 Simdjson-go 速度不够快?
SIMD (单指令流多数据流)是一组非凡的 CPU 指令,用于并行处理矢量化数据。目前,大多数 CPU 都反对 SIMD ,并宽泛用于图像处理和大数据计算。毫无疑问,SIMD在JSON解决中很有用(整形-字符串转换,字符搜寻等都是适合的场景)。咱们能够看到, simdjson-go 在大型 JSON 场景 (>100KB) 下十分有竞争力。然而,对于一些很小或不规则的字符字符串, SIMD 所需的额定加载操作将导致性能降落。因而,咱们须要思考不同的场景,并决定哪些场景应该应用 SIMD ,哪些不应该应用(例如,长度小于16字节的字符串)。
第二个问题来自 Go 编译器自身。为了保障编译速度, Golang 在编译阶段简直不进行任何优化工作也无奈间接应用编译器后端,如 LLVM 等进行优化。
那么,一些要害的计算函数是否用计算效率更高的其余语言编写吗?
C/Clang 是一种现实的编译工具(外部集成了 LLVM )。但要害是如何将优化后的汇编嵌入到 Golang 中。
如何更好地应用 Gjson
?
咱们还发现在单键查找场景中, gjson具备微小的劣势。这是因为它的查找是通过惰性加载机制实现的,奇妙地跳过了传递的值,并无效的缩小了许多不必要的解析。理论利用证实,在产品中充分利用这个个性的确能带来收益。然而,当波及到多键查找时,Gjson甚至比规范库还要差,这是其跳过机制的副作用——搜寻雷同门路会导致反复解析(跳过解析也是一种轻量的解析)因而,依据理论状况精确的做出调整是关键问题。
设计
基于以上问题,咱们的设计很好实现:
- 针对编解码动静汇编的函数调用开销,咱们应用 JIT 技术在运行时组装与模式对应的字节码(汇编指令),最终将其以 Golang 函数的模式缓存在堆外内存上。
- 针对大数据和小数据共存的理论场景,咱们应用预处理判断(字符串大小、浮点数精度等)将 SIMD 与标量指令相结合,从而实现对理论状况的最佳适应。
- 对于 Golang 语言编译优化的有余,咱们决定应用 C/Clang 编写和编译外围计算函数,并且开发了一套 asm2asm 工具,将通过充沛优化的 x86 汇编代码转换为 Plan9 格局,最终加载到 Golang 运行时中。
- 思考到解析和跳过解析之间的速度差别很大, 惰性加载机制当然也在咱们的 AST 解析器中应用了,但以一种更具适应性和高效性的形式来升高多键查问的开销。
在细节上,咱们进行了一些进一步的优化:
因为 Golang 中的原生汇编函数不能被内联,咱们发现其老本甚至超过了 C 编译器的优化所带来的改善。所以咱们在 JIT 中从新实现了一组轻量级的函数调用:
- 全局函数表+动态偏移量,用于调用指令
- 应用寄存器传递参数
Sync.Map
一开始被用来缓存编解码器,然而对于咱们的准动态(读远多于写),元素较少(通常有余几十个)的场景,它的性能并不现实,所以咱们应用凋谢寻址哈希和 RCU 技术从新实现了一个高性能且并发平安的缓存。
性能测试
性能测试脚本代码:
#!/usr/bin/env bashpwd=$(pwd)export SONIC_NO_ASYNC_GC=1cd $pwd/encodergo test -benchmem -run=^$ -benchtime=100000x -bench "^(BenchmarkEncoder_.*)$"cd $pwd/decodergo test -benchmem -run=^$ -benchtime=100000x -bench "^(BenchmarkDecoder_.*)$"cd $pwd/astgo test -benchmem -run=^$ -benchtime=1000000x -bench "^(BenchmarkGet.*|BenchmarkSet.*)$"go test -benchmem -run=^$ -benchtime=10000x -bench "^(BenchmarkParser_.*|BenchmarkEncode.*)$"go test -benchmem -run=^$ -benchtime=10000000x -bench "^(BenchmarkNodeGetByPath|BenchmarkStructGetByPath|BenchmarkNodeIndex|BenchmarkStructIndex|BenchmarkSliceIndex|BenchmarkMapIndex|BenchmarkNodeGet|BenchmarkSliceGet|BenchmarkMapGet|BenchmarkNodeSet|BenchmarkMapSet|BenchmarkNodeSetByIndex|BenchmarkSliceSetByIndex|BenchmarkStructSetByIndex|BenchmarkNodeUnset|BenchmarkMapUnset|BenchmarkNodUnsetByIndex|BenchmarkSliceUnsetByIndex|BenchmarkNodeAdd|BenchmarkSliceAdd|BenchmarkMapAdd)$"cd $pwd/external_jsonlib_test/benchmark_testgo test -benchmem -run=^$ -benchtime=100000x -bench "^(BenchmarkEncoder_.*|BenchmarkDecoder_.*)$"go test -benchmem -run=^$ -benchtime=1000000x -bench "^(BenchmarkGet.*|BenchmarkSet.*)$"go test -benchmem -run=^$ -benchtime=10000x -bench "^(BenchmarkParser_.*)$"unset SONIC_NO_ASYNC_GCcd $pwd
对于*所有大小*的 json 和*所有应用场景*, *Sonic 体现均为最佳*。
- 中型 (13kB, 300+ 键, 6 层)
goversion: 1.17.1goos: darwingoarch: amd64cpu: Intel(R) Core(TM) i9-9880H CPU @ 2.30GHzBenchmarkEncoder_Generic_Sonic-16 32393 ns/op 402.40 MB/s 11965 B/op 4 allocs/opBenchmarkEncoder_Generic_Sonic_Fast-16 21668 ns/op 601.57 MB/s 10940 B/op 4 allocs/opBenchmarkEncoder_Generic_JsonIter-16 42168 ns/op 309.12 MB/s 14345 B/op 115 allocs/opBenchmarkEncoder_Generic_GoJson-16 65189 ns/op 199.96 MB/s 23261 B/op 16 allocs/opBenchmarkEncoder_Generic_StdLib-16 106322 ns/op 122.60 MB/s 49136 B/op 789 allocs/opBenchmarkEncoder_Binding_Sonic-16 6269 ns/op 2079.26 MB/s 14173 B/op 4 allocs/opBenchmarkEncoder_Binding_Sonic_Fast-16 5281 ns/op 2468.16 MB/s 12322 B/op 4 allocs/opBenchmarkEncoder_Binding_JsonIter-16 20056 ns/op 649.93 MB/s 9488 B/op 2 allocs/opBenchmarkEncoder_Binding_GoJson-16 8311 ns/op 1568.32 MB/s 9481 B/op 1 allocs/opBenchmarkEncoder_Binding_StdLib-16 16448 ns/op 792.52 MB/s 9479 B/op 1 allocs/opBenchmarkEncoder_Parallel_Generic_Sonic-16 6681 ns/op 1950.93 MB/s 12738 B/op 4 allocs/opBenchmarkEncoder_Parallel_Generic_Sonic_Fast-16 4179 ns/op 3118.99 MB/s 10757 B/op 4 allocs/opBenchmarkEncoder_Parallel_Generic_JsonIter-16 9861 ns/op 1321.84 MB/s 14362 B/op 115 allocs/opBenchmarkEncoder_Parallel_Generic_GoJson-16 18850 ns/op 691.52 MB/s 23278 B/op 16 allocs/opBenchmarkEncoder_Parallel_Generic_StdLib-16 45902 ns/op 283.97 MB/s 49174 B/op 789 allocs/opBenchmarkEncoder_Parallel_Binding_Sonic-16 1480 ns/op 8810.09 MB/s 13049 B/op 4 allocs/opBenchmarkEncoder_Parallel_Binding_Sonic_Fast-16 1209 ns/op 10785.23 MB/s 11546 B/op 4 allocs/opBenchmarkEncoder_Parallel_Binding_JsonIter-16 6170 ns/op 2112.58 MB/s 9504 B/op 2 allocs/opBenchmarkEncoder_Parallel_Binding_GoJson-16 3321 ns/op 3925.52 MB/s 9496 B/op 1 allocs/opBenchmarkEncoder_Parallel_Binding_StdLib-16 3739 ns/op 3486.49 MB/s 9480 B/op 1 allocs/opBenchmarkDecoder_Generic_Sonic-16 66812 ns/op 195.10 MB/s 57602 B/op 723 allocs/opBenchmarkDecoder_Generic_Sonic_Fast-16 54523 ns/op 239.07 MB/s 49786 B/op 313 allocs/opBenchmarkDecoder_Generic_StdLib-16 124260 ns/op 104.90 MB/s 50869 B/op 772 allocs/opBenchmarkDecoder_Generic_JsonIter-16 91274 ns/op 142.81 MB/s 55782 B/op 1068 allocs/opBenchmarkDecoder_Generic_GoJson-16 88569 ns/op 147.17 MB/s 66367 B/op 973 allocs/opBenchmarkDecoder_Binding_Sonic-16 32557 ns/op 400.38 MB/s 28302 B/op 137 allocs/opBenchmarkDecoder_Binding_Sonic_Fast-16 28649 ns/op 455.00 MB/s 24999 B/op 34 allocs/opBenchmarkDecoder_Binding_StdLib-16 111437 ns/op 116.97 MB/s 10576 B/op 208 allocs/opBenchmarkDecoder_Binding_JsonIter-16 35090 ns/op 371.48 MB/s 14673 B/op 385 allocs/opBenchmarkDecoder_Binding_GoJson-16 28738 ns/op 453.59 MB/s 22039 B/op 49 allocs/opBenchmarkDecoder_Parallel_Generic_Sonic-16 12321 ns/op 1057.91 MB/s 57233 B/op 723 allocs/opBenchmarkDecoder_Parallel_Generic_Sonic_Fast-16 10644 ns/op 1224.64 MB/s 49362 B/op 313 allocs/opBenchmarkDecoder_Parallel_Generic_StdLib-16 57587 ns/op 226.35 MB/s 50874 B/op 772 allocs/opBenchmarkDecoder_Parallel_Generic_JsonIter-16 38666 ns/op 337.12 MB/s 55789 B/op 1068 allocs/opBenchmarkDecoder_Parallel_Generic_GoJson-16 30259 ns/op 430.79 MB/s 66370 B/op 974 allocs/opBenchmarkDecoder_Parallel_Binding_Sonic-16 5965 ns/op 2185.28 MB/s 27747 B/op 137 allocs/opBenchmarkDecoder_Parallel_Binding_Sonic_Fast-16 5170 ns/op 2521.31 MB/s 24715 B/op 34 allocs/opBenchmarkDecoder_Parallel_Binding_StdLib-16 27582 ns/op 472.58 MB/s 10576 B/op 208 allocs/opBenchmarkDecoder_Parallel_Binding_JsonIter-16 13571 ns/op 960.51 MB/s 14685 B/op 385 allocs/opBenchmarkDecoder_Parallel_Binding_GoJson-16 10031 ns/op 1299.51 MB/s 22111 B/op 49 allocs/opBenchmarkGetOne_Sonic-16 3276 ns/op 3975.78 MB/s 24 B/op 1 allocs/opBenchmarkGetOne_Gjson-16 9431 ns/op 1380.81 MB/s 0 B/op 0 allocs/opBenchmarkGetOne_Jsoniter-16 51178 ns/op 254.46 MB/s 27936 B/op 647 allocs/opBenchmarkGetOne_Parallel_Sonic-16 216.7 ns/op 60098.95 MB/s 24 B/op 1 allocs/opBenchmarkGetOne_Parallel_Gjson-16 1076 ns/op 12098.62 MB/s 0 B/op 0 allocs/opBenchmarkGetOne_Parallel_Jsoniter-16 17741 ns/op 734.06 MB/s 27945 B/op 647 allocs/opBenchmarkSetOne_Sonic-16 9571 ns/op 1360.61 MB/s 1584 B/op 17 allocs/opBenchmarkSetOne_Sjson-16 36456 ns/op 357.22 MB/s 52180 B/op 9 allocs/opBenchmarkSetOne_Jsoniter-16 79475 ns/op 163.86 MB/s 45862 B/op 964 allocs/opBenchmarkSetOne_Parallel_Sonic-16 850.9 ns/op 15305.31 MB/s 1584 B/op 17 allocs/opBenchmarkSetOne_Parallel_Sjson-16 18194 ns/op 715.77 MB/s 52247 B/op 9 allocs/opBenchmarkSetOne_Parallel_Jsoniter-16 33560 ns/op 388.05 MB/s 45892 B/op 964 allocs/opBenchmarkLoadNode/LoadAll()-16 11384 ns/op 1143.93 MB/s 6307 B/op 25 allocs/opBenchmarkLoadNode_Parallel/LoadAll()-16 5493 ns/op 2370.68 MB/s 7145 B/op 25 allocs/opBenchmarkLoadNode/Interface()-16 17722 ns/op 734.85 MB/s 13323 B/op 88 allocs/opBenchmarkLoadNode_Parallel/Interface()-16 10330 ns/op 1260.70 MB/s 15178 B/op 88 allocs/op
- 小型 (400B, 11 个键, 3 层)
- 大型 (635kB, 10000+ 个键, 6 层)
相干浏览
- Go切片底层原理 看这篇文章就够了
- 轻松了解Go中的内存逃逸问题
- 面试Go 被defer的几个盲区坑了