基于 JIT 技术的开源全场景高性能 JSON 库

news2024/10/7 12:17:21

大家好,我是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 层。

f62cdf3beca9468fbfc83f89de134ead~tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0

如何使用

依赖

  • 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
// Marshal
output, err := sonic.Marshal(&data)
// Unmarshal
err := sonic.Unmarshal(output, &data)

流式输入输出

Sonic 支持解码 io.Reader 中输入的 json,或将对象编码为 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())
// 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 float64
dc := decoder.NewDecoder(input)
dc.Decode(&data) // data == float64(1)
// use json.Number
dc = decoder.NewDecoder(input)
dc.UseNumber()
dc.Decode(&data) // data == json.Number("1")
// use int64
dc = decoder.NewDecoder(input)
dc.UseInt64()
dc.Decode(&data) // data == int64(1)

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

对键排序

考虑到排序带来的性能损失(约 10% ), sonic 默认不会启用这个功能。如果你的组件依赖这个行为(如 zstd) ,可以仿照下面的例子:

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

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

// Or ast.Node.SortKeys() before marshal
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 默认将基本类型( structmap 等)编码为紧凑格式的 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 json
root, err := sonic.Get(input)
raw := root.Raw() // == string(input)

// multiple paths
root, 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"

// Set
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

// Unset
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)     // WARN: use pointer
println(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.Nodeinterface{})。举个例子,你可能定义了下述的类型,它们看起来像 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) 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
}

详细用法参看 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=falseSortKeys=false等)。行为与具有相应配置的 encoding/json 一致,一些选项,如 SortKeys=false 将无效。
  • ConfigStd: 在支持 sonic 的环境下与标准库兼容的配置(EscapeHTML=trueSortKeys=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 代替 mapinterface 作为泛型的容器:

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() 来实现并发安全。尽管可能会带来性能损失,但仍比转换成 mapinterface{} 更为高效。

使用 ast.Node 还是 ast.Visitor

对于泛型数据的解析,ast.Node 在大多数场景上应该能够满足你的需求。

然而,ast.Node 是一种针对部分解析 JSON 而设计的泛型容器,它包含一些特殊设计,比如惰性加载,如果你希望像 Unmarshal() 那样直接解析整个 JSON,这些设计可能并不合适。尽管 ast.Node 相较于 mapinterface{} 来说是更好的一种泛型容器,但它毕竟也是一种中间表示,如果你的最终类型是自定义的,你还得在解析完成后将上述类型转化成你自定义的类型。

在上述场景中,如果想要有更极致的性能,ast.Visitor 会是更好的选择。它采用和 Unmarshal() 类似的形式解析 JSON,并且你可以直接使用你的最终类型去表示 JSON AST,而不需要经过额外的任何中间表示。

但是,ast.Visitor 并不是一个很易用的 API。你可能需要写大量的代码去实现自己的 ast.Visitor,并且需要在解析过程中仔细维护树的层级。如果你决定要使用这个 API,请先仔细阅读 ast/visitor.go 中的注释。

底层原理

在设计之初,字节研发团队做了如下几个问题的思考:

为什么 Json-iterator 比标准库快?

首先,标准库使用的基于模式(Schema)的处理机制是值得称赞的,解析器可以在扫描时提前获取元信息,从而缩短分支选择的时间。然而,它的原始实现没有很好地利用这个机制,而是花费了大量时间使用反射获取模式的元信息。与此同时,json-iterator 的方法是:将结构解释为逐个字段的编码和解码函数,然后将它们组装和缓存起来,最小化反射带来的性能损失。但这种方法是否一劳永逸呢?实际测试中,我们发现随着输入的 JSON 变深、变大,json-iterator 和其他库之间的差距逐渐缩小——甚至最终被超越:
introduction-1

原因是该实现转化为大量接口封装和函数调用,导致了函数调用的性能损失:

  1. 调用接口涉及到对 itab 的动态地址获取
  2. 组装的函数无法内联,而 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甚至比标准库还要差,这是其跳过机制的副作用——搜索相同路径会导致重复解析(跳过解析也是一种轻量的解析)因此,根据实际情况准确的做出调整是关键问题。

设计

基于以上问题,我们的设计很好实现:

  1. 针对编解码动态汇编的函数调用开销,我们使用 JIT 技术在运行时组装与模式对应的字节码(汇编指令),最终将其以 Golang 函数的形式缓存在堆外内存上。
  2. 针对大数据和小数据共存的实际场景,我们使用预处理判断(字符串大小、浮点数精度等)将 SIMD 与标量指令相结合,从而实现对实际情况的最佳适应。
  3. 对于 Golang 语言编译优化的不足,我们决定使用 C/Clang 编写和编译核心计算函数,并且开发了一套 asm2asm 工具,将经过充分优化的 x86 汇编代码转换为 Plan9 格式,最终加载到 Golang 运行时中。
  4. 考虑到解析和跳过解析之间的速度差异很大, 惰性加载机制当然也在我们的 AST 解析器中使用了,但以一种更具适应性和高效性的方式来降低多键查询的开销
    introduction-2

在细节上,我们进行了一些进一步的优化:

  1. 由于 Golang 中的原生汇编函数不能被内联,我们发现其成本甚至超过了 C 编译器的优化所带来的改善。所以我们在 JIT 中重新实现了一组轻量级的函数调用:
    • 全局函数表+静态偏移量,用于调用指令
    • 使用寄存器传递参数
  2. Sync.Map 一开始被用来缓存编解码器,但是对于我们的准静态(读远多于写),元素较少(通常不足几十个)的场景,它的性能并不理想,所以我们使用开放寻址哈希和 RCU 技术重新实现了一个高性能且并发安全的缓存。

性能测试

性能测试脚本代码:

#!/usr/bin/env bash

pwd=$(pwd)
export SONIC_NO_ASYNC_GC=1

cd $pwd/encoder
go test -benchmem -run=^$ -benchtime=100000x -bench "^(BenchmarkEncoder_.*)$"

cd $pwd/decoder
go test -benchmem -run=^$ -benchtime=100000x -bench "^(BenchmarkDecoder_.*)$"

cd $pwd/ast
go 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_test
go 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_GC
cd $pwd

对于****所有大小*的 json 和*所有使用场景****, *Sonic 表现均为最佳*

- 中型 (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

BenchmarkEncoder_Parallel_Generic_Sonic-16              6681 ns/op        1950.93 MB/s       12738 B/op          4 allocs/op

BenchmarkEncoder_Parallel_Generic_Sonic_Fast-16         4179 ns/op        3118.99 MB/s       10757 B/op          4 allocs/op

BenchmarkEncoder_Parallel_Generic_JsonIter-16           9861 ns/op        1321.84 MB/s       14362 B/op        115 allocs/op

BenchmarkEncoder_Parallel_Generic_GoJson-16            18850 ns/op         691.52 MB/s       23278 B/op         16 allocs/op

BenchmarkEncoder_Parallel_Generic_StdLib-16            45902 ns/op         283.97 MB/s       49174 B/op        789 allocs/op

BenchmarkEncoder_Parallel_Binding_Sonic-16              1480 ns/op        8810.09 MB/s       13049 B/op          4 allocs/op

BenchmarkEncoder_Parallel_Binding_Sonic_Fast-16         1209 ns/op        10785.23 MB/s      11546 B/op          4 allocs/op

BenchmarkEncoder_Parallel_Binding_JsonIter-16           6170 ns/op        2112.58 MB/s        9504 B/op          2 allocs/op

BenchmarkEncoder_Parallel_Binding_GoJson-16             3321 ns/op        3925.52 MB/s        9496 B/op          1 allocs/op

BenchmarkEncoder_Parallel_Binding_StdLib-16             3739 ns/op        3486.49 MB/s        9480 B/op          1 allocs/op

BenchmarkDecoder_Generic_Sonic-16                      66812 ns/op         195.10 MB/s       57602 B/op        723 allocs/op

BenchmarkDecoder_Generic_Sonic_Fast-16                 54523 ns/op         239.07 MB/s       49786 B/op        313 allocs/op

BenchmarkDecoder_Generic_StdLib-16                    124260 ns/op         104.90 MB/s       50869 B/op        772 allocs/op

BenchmarkDecoder_Generic_JsonIter-16                   91274 ns/op         142.81 MB/s       55782 B/op       1068 allocs/op

BenchmarkDecoder_Generic_GoJson-16                     88569 ns/op         147.17 MB/s       66367 B/op        973 allocs/op

BenchmarkDecoder_Binding_Sonic-16                      32557 ns/op         400.38 MB/s       28302 B/op        137 allocs/op

BenchmarkDecoder_Binding_Sonic_Fast-16                 28649 ns/op         455.00 MB/s       24999 B/op         34 allocs/op

BenchmarkDecoder_Binding_StdLib-16                    111437 ns/op         116.97 MB/s       10576 B/op        208 allocs/op

BenchmarkDecoder_Binding_JsonIter-16                   35090 ns/op         371.48 MB/s       14673 B/op        385 allocs/op

BenchmarkDecoder_Binding_GoJson-16                     28738 ns/op         453.59 MB/s       22039 B/op         49 allocs/op

BenchmarkDecoder_Parallel_Generic_Sonic-16             12321 ns/op        1057.91 MB/s       57233 B/op        723 allocs/op

BenchmarkDecoder_Parallel_Generic_Sonic_Fast-16        10644 ns/op        1224.64 MB/s       49362 B/op        313 allocs/op

BenchmarkDecoder_Parallel_Generic_StdLib-16            57587 ns/op         226.35 MB/s       50874 B/op        772 allocs/op

BenchmarkDecoder_Parallel_Generic_JsonIter-16          38666 ns/op         337.12 MB/s       55789 B/op       1068 allocs/op

BenchmarkDecoder_Parallel_Generic_GoJson-16            30259 ns/op         430.79 MB/s       66370 B/op        974 allocs/op

BenchmarkDecoder_Parallel_Binding_Sonic-16              5965 ns/op        2185.28 MB/s       27747 B/op        137 allocs/op

BenchmarkDecoder_Parallel_Binding_Sonic_Fast-16         5170 ns/op        2521.31 MB/s       24715 B/op         34 allocs/op

BenchmarkDecoder_Parallel_Binding_StdLib-16            27582 ns/op         472.58 MB/s       10576 B/op        208 allocs/op

BenchmarkDecoder_Parallel_Binding_JsonIter-16          13571 ns/op         960.51 MB/s       14685 B/op        385 allocs/op

BenchmarkDecoder_Parallel_Binding_GoJson-16            10031 ns/op        1299.51 MB/s       22111 B/op         49 allocs/op

BenchmarkGetOne_Sonic-16                                3276 ns/op        3975.78 MB/s          24 B/op          1 allocs/op

BenchmarkGetOne_Gjson-16                                9431 ns/op        1380.81 MB/s           0 B/op          0 allocs/op

BenchmarkGetOne_Jsoniter-16                            51178 ns/op         254.46 MB/s       27936 B/op        647 allocs/op

BenchmarkGetOne_Parallel_Sonic-16                      216.7 ns/op       60098.95 MB/s          24 B/op          1 allocs/op

BenchmarkGetOne_Parallel_Gjson-16                       1076 ns/op        12098.62 MB/s          0 B/op          0 allocs/op

BenchmarkGetOne_Parallel_Jsoniter-16                   17741 ns/op         734.06 MB/s       27945 B/op        647 allocs/op

BenchmarkSetOne_Sonic-16                               9571 ns/op         1360.61 MB/s        1584 B/op         17 allocs/op

BenchmarkSetOne_Sjson-16                               36456 ns/op         357.22 MB/s       52180 B/op          9 allocs/op

BenchmarkSetOne_Jsoniter-16                            79475 ns/op         163.86 MB/s       45862 B/op        964 allocs/op

BenchmarkSetOne_Parallel_Sonic-16                      850.9 ns/op       15305.31 MB/s        1584 B/op         17 allocs/op

BenchmarkSetOne_Parallel_Sjson-16                      18194 ns/op         715.77 MB/s       52247 B/op          9 allocs/op

BenchmarkSetOne_Parallel_Jsoniter-16                   33560 ns/op         388.05 MB/s       45892 B/op        964 allocs/op

BenchmarkLoadNode/LoadAll()-16                         11384 ns/op        1143.93 MB/s        6307 B/op         25 allocs/op

BenchmarkLoadNode_Parallel/LoadAll()-16                 5493 ns/op        2370.68 MB/s        7145 B/op         25 allocs/op

BenchmarkLoadNode/Interface()-16                       17722 ns/op         734.85 MB/s       13323 B/op         88 allocs/op

BenchmarkLoadNode_Parallel/Interface()-16              10330 ns/op        1260.70 MB/s       15178 B/op         88 allocs/op

- 小型 (400B, 11 个键, 3 层)

bench-small

- 大型 (635kB, 10000+ 个键, 6 层)

bench-large

相关阅读

  • Go切片底层原理 看这篇文章就够了
  • 轻松理解Go中的内存逃逸问题
  • 面试Go 被defer的几个盲区坑了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/750984.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于亚博K210开发板——LED(RGB)点灯

文章目录 开发板实验目的实验准备查看原理图软件对应SDKGPIO配置函数什么是 FPIOA 呢 实验代码LED/RGB驱动主程序控制 实验结果 开发板 实验目的 实现开发板上LED0、LED1以及RGB灯的点亮 实验准备 查看原理图 K210 开发板出厂默认已经焊接好 LED0 和 LED1。LED0 连接的是 IO…

Acrel-1000DP分布式光伏系统在重工企业的应用

安科瑞 崔丽洁 摘 要&#xff1a;分布式光伏发电特指在用户场地附近建设&#xff0c;运行方式以用户侧自发自用、余电上网&#xff0c;且在配电系统平衡调节为特征的光伏发电设施&#xff0c;是一种新型的、具有广阔发展前景的发电和能源综合利用方式&#xff0c;它倡导就近发电…

2D、3D机器视觉各有优势与局限,融合应用将成工业领域生产新方式

在智能制造的浪潮中&#xff0c;制造行业生产线亟需转型升级&#xff0c;为国内机器视觉市场释放出了惊人的机器视觉技术及产品需求。在自动化工业质量控制和在线检测领域&#xff0c;2D机器视觉与3D机器视觉都具有重要的作用。那在机器视觉自动化场景中该如何选择合适的机器视…

udp传输大数据的原理和相关问题注意事项

UDP协议本身不提供大数据传输的分片、重组、丢包重传等功能&#xff0c;因此需要对大数据传输进行特殊处理。以下是UDP传输大数据一些常见的处理方法。 &#xff08;1&#xff09;对大文件进行分块&#xff1a;将大文件划分为多个小块进行传输&#xff0c;每个小块都可以使用UD…

机器学习指标计算

一、有如下图 accuracy accuracy T P T N T P T N F P F N \text { accuracy }\frac{T PT N}{T PT NF PF N} accuracy TPTNFPFNTPTN​ accuracy指的是正确预测的样本数占总预测样本数的比值&#xff0c;它不考虑预测的样本是正例还是负例,考虑的是全部样本。 precision&…

【Ubuntu源码安装PostgreSQL】

Ubuntu源码安装PostgreSQL 1. PostgreSQL官网下载压缩包2. 解压&安装2.1 解压文件2.2 安装依赖2.3 执行安装2.4 执行安装2.5 添加路径到文件 3. 初始化数据库与使用3.1 初始化数据库3.2 启动数据库服务3.3 启动数据库 1. PostgreSQL官网下载压缩包 下载地址&#xff1a;ht…

基于linux下的高并发服务器开发(第一章)- 动态库加载失败的原因1.7

01 / 动态库的制作 02 / 工作原理 静态库&#xff1a;GCC进行链接时&#xff0c;会把静态库中代码打包到可执行程序中动态库&#xff1a;GCC进行链接时&#xff0c;动态库的代码不会被打包到可执行程序中程序启动之后&#xff0c;动态库会被动态加载到内存中&#xff0c;通过 …

Spring Cloud之Config分布式配置应⽤

. 右键⽗⼯程【 yx-parent 】选择【 New 】 - 【 Module 】选项&#xff0c;然后选择创建【 Maven 】类型项⽬&#xff08;不勾选模 板&#xff09;&#xff0c;将项⽬名称设置为【yx-cloud-config 】。 在yx-cloud-config⼯程的pom.xml⽂件中引⼊以下依赖坐标&#xff08;需要…

AI原生云向量数据库Zilliz Cloud查看备份快照和恢复备份

目录 快照状态 查看快照详情 相关文档 本文介绍如何查看已创建的快照信息。您可以手动或自动创建快照。 快照状态 根据不同场景,快照可能会处于如下状态: 创建中 如果快照处于此状态,则无法创建其他快照。但您可以单击操作列中的更多按钮,然后选择取消来终止创建过程。…

SpringCloud——分布式请求链路跟踪Sleuth

安装运行zipkin SpringCloud从F版已不需要自己构建Zipkin Server&#xff0c;只需要调用jar包即可 https://dl.bintray.com/oenzipkin/maven/io/zipkin/java/zipkin-server/ 下载&#xff1a;zipkin-server-2.12.9-exec.jar 运行&#xff1a;java -jar zipkin-server-2.12.9-e…

【Excel】excel多个单元格的内容合并到一个单元格,并使用分隔符

方法一&#xff1a;使用连接符 & 左键单击选中“D2”单元格&#xff0c;在D2单元格中输入公式“A2&B2&C2”&#xff0c;按“Enter”即可实现数据合并。 ------如果想连接的时候&#xff0c;中间加分隔符&#xff0c;可以使用&#xff1a;公式A2&"&#xf…

4G无线红外电表电能量力参数采集仪器功能DL/T645-1997、DL/T645-2007、DL∕T698.45-2017

红外抄表终端应支持4G通信&#xff0c;满足电信、移动、联通全网通的通信制式&#xff1b; &#xff08;2&#xff09;采集终端支持并兼容DL/T645-1997、DL/T645-2007、DL∕T698.45-2017版本电表&#xff1b; &#xff08;3&#xff09;采集终端以IR红外为数据采集方式&#xf…

pycharm import的类库修改后要重启问题的解决方法

通过将以下行添加到pycharm中的settings-> Build,Excecution,Deployment-> Console-> Python Console中&#xff0c;可以指示Pycharm在更改时自动重新加载模块&#xff1a; %load_ext autoreload %autoreload 2

SOLIDWORKS如何定制化异型孔向导及其孔标注

异型孔向导是SOLIDWORKS中一个极为方便的功能&#xff0c;只需要简单的几步设置就可以根据工程师设定好的标准、类型、大小创建符合标准的孔(包括:柱形沉头孔、锥形沉头孔、直孔、直螺纹孔、锥形螺纹孔等等)。此外由于工程图和三维模型的协同关联&#xff0c;生成孔的公差信息、…

Java将获取的参数,图片以及pdf文件放入到word文档指定位置

首先引入的依赖 <!-- poi库 --> <dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>4.1.2</version></dependency><dependency><groupId>org.apache.poi</groupId&…

VQA评测evaluation代码:gqa / aokvqa / vqav2 / scienceQA

VQA评测分多种&#xff0c;这里提几种&#xff0c;代码参考来自lavis和mmpretrain。 一、gqa评测&#xff08;只有一个answer&#xff09; 数据集下载及格式&#xff1a; blip中json地址 图片下载 # gqa格式已重新整理&#xff0c;特点是每个question对应的gt_answers只有一…

Linux操作系统知识点总结(附VMware、CentOS以及finalshell的安装教程)

1. 计算机的组成部分&#xff1a;输入单元&#xff0c;中央处理器&#xff08;CPU&#xff09;&#xff0c;输出单元。 CPU的种类包括&#xff1a;精简指令集&#xff08;RISC&#xff09;和复杂指令集&#xff08;CISC&#xff09;。 计算机的五大单元包括输入单元、输出单元、…

vue3+element+sortablejs实现table表格 行列动态拖拽

vue3elementsortablejs实现table动态拖拽 1.第一步我们要安装sortablejs依赖2.在我们需要的组件中引入3.完整代码4.效果 1.第一步我们要安装sortablejs依赖 去博客设置页面&#xff0c;选择一款你喜欢的代码片高亮样式&#xff0c;下面展示同样高亮的 代码片. npm install so…

【力扣算法09】之 6. N 字形变换 python

文章目录 问题描述示例1示例2示例3提示 思路分析代码分析完整代码详细分析运行效果截图调用示例运行结果 完结 问题描述 将一个给定字符串 s 根据给定的行数 numRows &#xff0c;以从上往下、从左到右进行 Z 字形排列。 比如输入字符串为 “PAYPALISHIRING” 行数为 3 时&…

组合(力扣)dfs + 回溯 + 剪枝 JAVA

给定两个整数 n 和 k&#xff0c;返回范围 [1, n] 中所有可能的 k 个数的组合。 你可以按 任何顺序 返回答案。 示例 1&#xff1a; 输入&#xff1a;n 4, k 2 输出&#xff1a; [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4], ] 示例 2&#xff1a; 输入&#xff1a;n 1, …