博客 / 詳情

返回

基於 JIT 技術的開源全場景高性能 JSON 庫

大家好,我是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的幾個盲區坑了
user avatar shockerli 頭像 roseduan 頭像 rwxe 頭像
3 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.