探究|Go JSON 三方包哪家強?

来源:https://www.cnblogs.com/88223100/archive/2023/06/04/Which-is-the-strongest-third-party-package-for-Go-JSON.html
-Advertisement-
Play Games

本文作者從評判標準、功能評測、性能評測等多方面剖析三方庫哪些家強,並給出了比較務實的建議。 引言 為了小伙伴理解,彙總了一下文章中會提及的術語名詞解釋,請放心品讀,歡迎一起討論交流! 你真的瞭解 Go 標準庫嗎? 問題一:標準庫可以反序列化普通的字元串嗎?執行下麵的代碼會報錯嗎? var s str ...


本文作者從評判標準、功能評測、性能評測等多方面剖析三方庫哪些家強,並給出了比較務實的建議。

引言

為了小伙伴理解,彙總了一下文章中會提及的術語名詞解釋,請放心品讀,歡迎一起討論交流!

圖片

 

你真的瞭解 Go 標準庫嗎?

問題一:標準庫可以反序列化普通的字元串嗎?執行下麵的代碼會報錯嗎?


var s string
err := json.Unmarshal([]byte(`"Hello, world!"`), &s)
assert.NoError(t, err)
fmt.Println(s)
// 輸出:
// Hello, world!

解:其實標準庫解析不僅支持是對象、數組,同時也可以是字元串、數值、布爾值以及空值,但需要註意,上面字元串中的雙引號不能缺,否則將不是一個合法的 json 序列,會返回錯誤。

問題二:如果結構體的 json tag 定義與 key 大小不一致,可以反序列化成功嗎?

cert := struct {
    Username string `json:"username"`
    Password string `json:"password"`
}{}
err = json.Unmarshal([]byte(`{"UserName":"root","passWord":"123456"}`), &cert)
if err != nil {
    fmt.Println("err =", err)
} else {
    fmt.Println("username =", cert.Username)
    fmt.Println("password =", cert.Password)
}
// 輸出:
// username = root
// password = 123456

解:如果遇到大小寫問題,標準庫會儘可能地進行大小寫轉換,即:一個 key 與結構體中的定義不同,但忽略大小寫後是相同的,那麼依然能夠為欄位賦值。

為什麼使用第三方庫,標準庫有哪些不足?

Go json 標準庫 encoding/json[1]已經是提供了足夠舒適的 json 處理工具,廣受 Go 開發者的好評,但還是存在以下兩點問題:
  • API 不夠靈活:如沒有提供按需載入機制等;

  • 性能不太高:標準庫大量使用反射獲取值,首先 Go 的反射本身性能較差,較耗費 CPU 配置;其次頻繁分配對象,也會帶來記憶體分配和 GC 的開銷;
基於上面的考量,業務會根據使用場景、降本收益等訴求,引入合適的第三方庫。

三方庫哪些家強?

首先,思考從提問開始:
  • 熱門的三方庫有哪些?
  • 內部實現原理是什麼?
  • 如何結合業務去選型?

下麵是我收集整理的一些開源第三方庫,也歡迎有興趣的小伙伴一起交流補充!
庫名 encoder decoder compatible star 數 (2023.04.19) 社區維護性
StdLib(encoding/json)[2] ✔️ ✔️ N/A - -
FastJson(valyala/fastjson)[3] ✔️ ✔️ 1.9k 較差
GJson(tidwall/gjson)[4] ✔️ ✔️ 12.1k 較好
JsonParser(buger/jsonparser)[5] ✔️ ✔️ 5k 較差
JsonIter(json-iterator/go)[6] ✔️ ✔️ 部分相容 12.1k 較差
GoJson(goccy/go-json)[7] ✔️ ✔️ ✔️ 2.2k 較好
EasyJson(mailru/easyjson)[8] ✔️ ✔️ 4.1k 較差
Sonic(bytedance/sonic)[9] ✔️ ✔️ ✔️ 4.1k 較好

評判標準

評判標準包含三個維度:

  • 性能:內部實現原理是什麼,是否使用反射機制;

  • 穩定性:考慮到要投入生產使用,必須是一個較為穩定的三方庫;

  • 功能靈活性:是否支持 Unmarshal 到 map 或 struct,是否提供的一些定製化抽取的 API;
在功能劃分上,根據主流 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 並處理。

功能評測

圖片

特點分析

圖片

性能評測

下麵是評測性能時所用的各個包的版本情況,具體的測試代碼,可參考benchmark_test [10]。

圖片

為什麼沒有評測 GJson 庫呢?
GJson 在單鍵查找的場景下有很大的優勢。這是因為它的查找是通過 lazy-load 實現的,它巧妙地跳過了傳遞值,有效地減少了很多不必要的解析,但其實跳過也是一種輕量級解析,實際是在處理 json 控制字元“[”、“{”等;然而當涉及到多鍵查找時,Gjson 做的事比標準庫更糟糕,這是其跳過機制的副作用:相同路徑查找導致的重覆開銷。針對它的使用場景,單純的評測定性編解碼和泛型編解碼,對 GJson 是不公平的。
根據樣本 json 的 key 數量和深度分為三個量級:
  • Small[11](400B, 11 keys, 3 layers)
  • Medium[12](13KB, 300+ key, 6 layers)
  • Large[13](635KB, 10000+ key, 6 layers)

評測過程中,需要註意不但要區分泛型編/解碼、定型編/解碼,而且也要考慮到併發情況下的性能表現,測試代碼樣例如下:
func BenchmarkEncoder_Generic_StdLib(b *testing.B) {
    _, _ = json.Marshal(_GenericValue)
    b.SetBytes(int64(len(TwitterJson)))
    b.ResetTimer()
for i := 0; i < b.N; i++ {
        _, _ = json.Marshal(_GenericValue)
    }
}
func BenchmarkEncoder_Binding_StdLib(b *testing.B) {
    _, _ = json.Marshal(&_BindingValue)
    b.SetBytes(int64(len(TwitterJson)))
    b.ResetTimer()
for i := 0; i < b.N; i++ {
        _, _ = json.Marshal(&_BindingValue)
    }
}
func BenchmarkEncoder_Parallel_Generic_StdLib(b *testing.B) {
    _, _ = json.Marshal(_GenericValue)
    b.SetBytes(int64(len(TwitterJson)))
    b.ResetTimer()
    b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
            _, _ = json.Marshal(_GenericValue)
        }
    })
}
func BenchmarkEncoder_Parallel_Binding_StdLib(b *testing.B) {
    _, _ = json.Marshal(&_BindingValue)
    b.SetBytes(int64(len(TwitterJson)))
    b.ResetTimer()
    b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
            _, _ = json.Marshal(&_BindingValue)
        }
    })
}

具體的指標數據和統計結果,可參考benchmark_readme [14],總體結論如下:

圖片


常見優化思路有哪些?

定型編解碼

對於有 schema 的定型編解碼場景而言,很多運算其實不需要在“運行時”執行。例如業務模型中確定了某個 json key 的值一定是布爾類型,其實可以在序列化階段直接輸出這個對象對應的 json 值(true/false),並不需要再檢查這個對象的具體類型。其核心思想是優化數據處理邏輯,將模型解釋與數據處理邏輯分離,讓前者提前固定下來,進而消除反射,提升性能。

動態函數組裝

JsonIter 就使用函數組裝模式,具體方法是將結構解釋為逐個欄位的編碼和解碼函數,然後將其組裝並緩存為整個對象對應的編解碼器,運行時再載入出來處理 json,將反射的性能損失成本降到最低。
這樣優化是否就能一勞永逸呢?
並沒有。因為這樣實現會轉化為大量的介面封裝和函數調用棧。在實際測試中,會發現隨著 json 數據量級的增長,因為調用介面涉及動態定址,彙編函數不能被內聯,而 Golang 的函數調用性能很差(沒有逐個寄存器的參數傳遞),函數調用的開銷也會成倍放大。

減少函數調用

為了避免動態彙編的函數調用開銷,業界實現方式目前主要有兩種:code-gen(代碼生成 ) 和 JIT( 即時編譯)。

code-gen

EasyJson 就採用代碼生成的思路。這種實現方式庫開發者實現起來相對簡單,性能高;但是它伴隨著模式依賴性和便利性的損失,增加業務代碼的維護成本和局限性,無法做到秒級熱更新,這也是代碼生成方式的 json 庫受眾並不廣泛的原因。

JIT

即時編譯將編譯過程移到了程式的載入或首次解析階段,只需要提供 json schema 對應的結構體類型信息,就可以一次性編譯生成對應的編解碼器,通常以 Golang 函數的形式緩存到堆外記憶體,便於後期高效執行。
// 函數緩存
type cache struct {
  functions map[*rtype]function
lock      sync.Mutex
}
var (
global = func() [caches]*cache {
var caches [caches]*cache
for idx := range caches {
      caches[idx] = &cache{functions: make(map[*rtype]function, 4)}
    }
return caches
  }()
)
func load(typ *rtype) (function, bool) {
do, ok := global[uintptr(unsafe.Pointer(typ))%caches].functions[typ]
return do, ok
}
func save(typ *rtype, do function) {
cache := global[uintptr(unsafe.Pointer(typ))%caches]
  cache.lock.Lock()
  cache.functions[typ] = do
  cache.lock.Unlock()
}

泛型編解碼

其實泛型編解碼性能差不只是因為沒有 schema ,因為可以對比一下 C++ 的 json 庫,如 simdjson[15],它的解析方式都是泛型的,但性能仍然很好;
Go 標準庫泛型解析性能差,原因在於它採用了 map[string]interface{} 作為 json 的編解碼對象。這其實是一種糟糕的選擇,原因如下:
  • 數據反序列化的過程中,map 插入的開銷很高;
  • 在數據序列化過程中,map 遍歷也遠不如數組高效;

如果用一種與 json AST 更貼近的數據結構來描述,不但可以讓轉換過程更加簡單,甚至可以實現 lazy-load 。

復用編碼緩衝區

通過使用 sync.Pool 復用先前編碼的緩衝區,可以有效減少 encode buffer 的記憶體分配次數。

type buffer struct {
    data []byte
}
var bufPool = sync.Pool{
    New: func() interface{} {
return &buffer{data: make([]byte, 0, 1024)}
    },
}
// 復用緩衝區
buf := bufPool.Get().(*buffer)
data := encode(buf.data)
newBuf := make([]byte, len(data))
copy(newBuf, buf)
buf.data = data
bufPool.Put(buf)

Sonic 庫為什麼性能好?

原理調研

Sonic 基於彙編進行開發,通過充分利用向量化(SIMD)指令、優化記憶體佈局和按需解析等關鍵技術,大幅提高了序列化反序列化性能。
它的優化思路可以分成離線和線上:
  • 離線場景:針對 Go 語言編譯優化的不足,Sonic 核心計算函數使用 C 語言編寫,使用 Clang 的深度優化編譯選項,並開發了一套 asm2asm 工具,將完全優化的 x86 彙編翻譯成 plan9 彙編,載入到 Golang 運行時,以供調用。

  • 線上場景:通過自定義 AST,實現了按需載入;採用 JIT 技術在運行時對模式對應的操作碼進行裝配,以 Golang 函數的形式緩存到堆外記憶體。這樣大大減少函數調用,同時也保證靈活性;

圖片

為什麼不使用 CGO ?
雖然使用 CGO 實現更加簡便, 但 CGO 在調用 c 代碼的時候引入了調度、切換線程棧等開銷,會造成較大(有的場景中高達 20 多倍)的性能損耗,無法對代碼進行了深度優化。

SIMD

什麼是 SIMD?
SIMD(Single-Instruction-Multi-Data 單指令流多數據流)它是一種採用一個控制器來控制多個處理器,同時對一組數據中的每一個數據分別執行相同的操作,從而實現空間上的並行性技術。例如:X86 的 SSE 或者 AVX2 指令集,以及 ARM 的 NEON 指令集等。它作為一組特殊的 CPU 指令,用於矢量數據的並行處理。目前,它被大多數 CPU 所支持,並廣泛用於圖像處理和大數據計算中,當然在 json 處理中也很有用。
SIMD 在 json 處理中解決了什麼問題?
對於 json 文本的處理與計算。其中一些問題在業界已經有比較成熟高效的解決方案,如浮點數轉字元串演算法 Ryu,整數轉字元串的查表法等;針對一些問題邏輯相對簡單,但是可能會面對較大數量級的文本,如 json string 的 unquote\quote 處理、空白字元的跳過等,也需要某種技術手段來提升處理能力,而 SIMD 就是一種用於並行處理大規模數據的技術。simdjson-go [16]在大型 json 場景(>100KB)中非常有競爭力。然而,對於一些極小的或不規則的字元串,SIMD 所需的額外負載操作將導致性能下降。因此, 對於大數據和小數據並存的實際場景,採用預設條件判斷(字元串大小、浮動精度等),將 SIMD 和標量指令結合起來,以達到最佳適應性。

asm2asm

為了提高執行效率,Sonic 中一些關鍵的計算函數是用 C 編寫的,用 Clang 編譯的;但由於 Clang 編譯出來的是 x86 彙編,而 Golang 編譯出來的是 plan9 彙編;那如何將優化後的彙編嵌入 Golang 中就變成了一個問題,因此為了在 Golang 中調用 Clang 編譯出來的彙編,位元組開發了一個內部工具 tools/asm2asm 將 x86 的彙編轉換為 plan9。

JIT 彙編

Sonic 借鑒 JsonIter 的組裝各類型處理函數的實現,針對編解碼器動態裝配的函數調用開銷,採用 JIT 技術在運行時對模式對應的操作碼(asm)進行裝配,最後以 Golang 函數的形式緩存到堆外記憶體。因為編譯後的編解碼函數是一個集成函數,可以大大減少函數調用,同時保證靈活性;對於 json 對應的結構體已知的服務場景,線上進行 JIT 彙編是比較耗時的,會導致首次請求耗時較高,也可以預先生成好彙編後的位元組碼。

RCU cache

為了提升 Codec Cache 的載入速度,每個結構體對應的序列化/反序列化位元組碼可以緩存起來,後面直接調用,以減少運行時彙編操作的執行次數(緩存足夠大的時候只需要執行一次)。Sync.Map 最初是用來緩存編解碼器的,但對於準靜態(讀遠多於寫)、較少元素(通常不超過幾十個)的場景來說,其性能並不理想,所以用“開放定址哈希+RCU 技術”重新實現了一個高性能和併發安全的緩存。

自定義 AST

針對泛型編解碼,基於 map 開銷較大的考慮,Sonic 實現了更符合 json 結構的樹形 AST;通過自定義的一種通用的泛型數據容器 sonic-ast 替代 Go interface,從而提升性能。

用 node {type, length, pointer} 表示任意一個 json 數據節點,並結合樹與數組結構描述節點之間的層級關係。針對部分解析,考慮到解析和跳過之間的巨大速度差距,將 lazy-load 機制到 AST 解析器中,以一種更加自適應和高效的方式來減少多鍵查詢的開銷。


type Node struct {
v int64
t types.ValueType
p unsafe.Pointer
}

如何實現部分解析?

sonic-ast 實現了一種有狀態、可伸縮的 json 解析過程。當使用者 get 某個 key 時,採用 skip 計算來輕量化跳過要獲取的 key 之前的 json 文本;對於該 key 之後的 json 節點,直接不做任何的解析處理;僅使用者真正需要的 key 才完全解析。

如何解決相同路徑查找重覆開銷的問題?

在對於子節點 skip 處理過程增加了一個步驟,將跳過 json 的 key、起始位、結束位記錄下來,分配一個 Raw-JSON 類型的節點保存下來,這樣二次 skip 就可以直接基於節點的 offset 進行,這樣解決相同路徑查找導致的重覆開銷的問題。同時 sonic-ast 支持了節點的更新、插入和序列化,甚至支持將任意 Go types 轉為節點並保存下來。

圖片

函數調用優化

  • 無棧記憶體管理:自己維護變數棧(記憶體池),避免 Go 函數棧擴展。

  • 自動生成跳轉表,加速 generic decoding 的分支跳轉。

  • 使用寄存器傳參:儘量避免 memory load & store,將使用頻繁的變數放到固定的寄存器上,如:json buffer、結構體指針;

  • 重寫函數調用:由於彙編函數不能內聯到 Go 函數中,函數調用引入的開銷甚至會抵消 SIMD 帶來的性能提升,因此在 JIT 中重新實現了一組輕量級的函數調用(維護全局函數表+函數 offset)。

業務實踐

適用場景

由於 Sonic 優化的是 json 操作,所以在 json 操作的 cpu 開銷占比較大的服務場景中收益會比較明顯。比如網關、轉發和入口服務等。

快速試用

為了較小侵入地驗證 Sonic 會對服務產生的性能提升,評估是否值得切換。推薦使用brahma-adshonor/gohook [17]工具庫,內部大概實現是向被 hook 的函數地址中寫入跳轉指令,直接跳轉到新的函數地址。
使用方式:在 main 函數的入口處 hook 當前使用的 json 庫函數為 Sonic 中對等函數。hook 是函數級的,因此可以具體驗證具體函數的性能提升;當人出於對某些函數的不信任、或者自己有性能更優異或更穩定的實現,也可以部分函數使用 Sonic。但需要註意,它未經過生產環境驗證,建議僅測試使用。切記,線上變更時,一定要滿足“可監控”,“可灰度”、“可回滾”的三大原則。

import "github.com/brahma-adshonor/gohook"
func main() {
// 在main函數的入口hook當前使用的json庫(如encoding/json)
    gohook.Hook(json.Marshal, sonic.Marshal, nil)
    gohook.Hook(json.Unmarshal, sonic.Unmarshal, nil)
}

收益情況

截止 2022 年 1 月份,Sonic 已應用於抖音,今日頭條等服務,累計為位元組節省了數十萬核。下圖為位元組某服務使用 Sonic 後高峰時段的 cpu 占用核數對比(圖來源sonic :基於 JIT 技術的開源全場景高性能 JSON 庫)。在生產環境中,Sonic 中也驗證了良好的收益,服務高峰期占用核數減少將近三分之一:

圖片

同時我們將線上的一個服務,將 HTTP 查詢介面 JSON 序列化由標準庫切換為 Sonic 庫後,在相同 QPS 量級下,CPU 利用率下降了接近 3 個點,效果也還不錯。

圖片

使用事項

HTML Escape

標準庫中預設會開啟 html Escape,而 Sonic 出於性能損耗預設不開啟。

func TestEncode(t *testing.T) {
  data := map[string]string{"&&": "<>"}
// 標準庫
var w1 = bytes.NewBuffer(nil)
  enc1 := json.NewEncoder(w1)
  err := enc1.Encode(data)
  assert.NoError(t, err)
// Sonic 庫
var w2 = bytes.NewBuffer(nil)
  enc2 := encoder.NewStreamEncoder(w2)
  err = enc2.Encode(data)
  assert.NoError(t, err)
  fmt.Printf("%v%v", w1.String(), w2.String())
}
// 運行結果:
{"\u0026\u0026":"\u003c\u003e"}
{"&&":"<>"}

若有需要可以通過下麵方式開啟:


import "github.com/bytedance/sonic/encoder"
v := map[string]string{"&&":"<>"}
ret, err := encoder.Encode(v, EscapeHTML) // ret == `{"\u0026\u0026":{"X":" \u003e"}}`
enc := encoder.NewStreamEncoder(w)
enc.SetEscapeHTML(true)
err := enc.Encode(obj)

大型模式問題

由於 Sonic 使用 golang-asm 作為 JIT 彙編器,這不太適合運行時編譯,因此首次運行大型模式可能會導致請求超時甚至處理 OOM。為了獲得更好的穩定性,建議在 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),
    )
}

key 排序

Sonic 在序列化時預設是不對 key 進行排序的。json 的規範也與順序無關,但若需要 json 是有序的,可以在序列化時選擇排序的配置,大約會帶來 10%的性能損耗。排序方法如下:

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()

暫不支持 arm 架構

現象:用 Mac M1 無法編譯成功,解決方案可參考 sonic-compatibility[19],在編譯時通過添加以下參數: GOARCH=amd64,可解決編譯失敗的問題,但依舊無法支持本地 Debug 操作;
官方在 issue172 [20]中曾表示因內部實現的原因,這個問題的確很難搞,但還是有望在 Sonic V2 大版本中支持。

總結

綜上,業務選型上需要根據具體情況、不同領域的業務使用場景和發展趨勢進行選擇,綜合考慮各方面因素。最適配業務的才是最好的! 例如:如果業務只是簡單的解析 http 請求返回的 json 串的部分欄位,並且欄位都是確定的,偶爾需要搜索功能,那 Gjson 是很不錯的選擇。
以下是一些個人觀點,僅供參考:
  • 不太推薦使用 Jsoniter 庫,原因在於: Go 1.8 之前,官方 Json 庫的性能就收到多方詬病。不過隨著 Go 版本的迭代,標準 json 庫的性能也越來越高,Jsonter 的性能優勢也越來越窄。如果希望有極致的性能,應該選擇 Easyjson 等方案而不是 Jsoniter,而且 Jsoniter 近年已經不活躍了。

  • 比較推薦使用 Sonic 庫,因不論從性能和功能總體而言,Sonic 的表現的確很亮眼;此外,通過瞭解 Sonic 的內部實現原理,提供一種對於 cpu 密集型操作優化的“野路子”,即:通過編寫高性能的 C 代碼並經過優化編譯後供 Golang 直接調用。其實並不新鮮,因為實際上 Go 源碼中的一些 cpu 密集型操作底層就是編譯成了彙編後使用的,如:crypto 和 math。

參考資料:

  • 深入 Go 中各個高性能 JSON 解析庫:https://www.luozhiyun.com/archives/535
  • Go 語言原生的 json 包有什麼問題?如何更好地處理 JSON 數據?:https://cloud.tencent.com/developer/article/1820473
  • go-json#how-it-works:https://github.com/goccy/go-json#how-it-works
  • sonic :基於 JIT 技術的開源全場景高性能 JSON 庫
  • Introduction to Sonic:https://github.com/bytedance/sonic/blob/main/INTRODUCTION.md
  • bytedance/sonic-readme:https://pkg.go.dev/github.com/bytedance/[email protected]#section-readme
  • 為位元組節省數十萬核的 json 庫 sonic:https://zhuanlan.zhihu.com/p/586050976

[1]https://pkg.go.dev/encoding/json?spm=ata.21736010.0.0.6e462b76wytEry

[2]https://pkg.go.dev/encoding/json?spm=ata.21736010.0.0.6e462b76wytEry

[3]https://github.com/valyala/fastjson?spm=ata.21736010.0.0.6e462b76wytEry

[4]https://github.com/tidwall/gjson?spm=ata.21736010.0.0.6e462b76wytEry

[5]https://github.com/buger/jsonparser?spm=ata.21736010.0.0.6e462b76wytEry

[6]https://github.com/json-iterator/go?spm=ata.21736010.0.0.6e462b76wytEry

[7]https://github.com/goccy/go-json?spm=ata.21736010.0.0.6e462b76wytEry

[8]https://github.com/mailru/easyjson?spm=ata.21736010.0.0.6e462b76wytEry

[9]https://github.com/bytedance/sonic?spm=ata.21736010.0.0.6e462b76wytEry

[10]https://github.com/zhao520a1a/go-base/blob/master/json/benchmark_test/bench.sh?spm=ata.21736010.0.0.6e462b76wytEry&file=bench.sh

[11]https://github.com/zhao520a1a/go-base/blob/master/json/testdata/small.go?spm=ata.21736010.0.0.6e462b76wytEry&file=small.go

[12]https://github.com/zhao520a1a/go-base/blob/master/json/testdata/medium.go?spm=ata.21736010.0.0.6e462b76wytEry&file=medium.go

[13]https://github.com/zhao520a1a/go-base/blob/master/json/testdata/large.json?spm=ata.21736010.0.0.6e462b76wytEry&file=large.json

[14]https://github.com/zhao520a1a/go-base/blob/master/json/benchmark_test/README.md?spm=ata.21736010.0.0.6e462b76wytEry&file=README.md

[15]https://github.com/simdjson/simdjson?spm=ata.21736010.0.0.6e462b76wytEry

[16]https://github.com/minio/simdjson-go?spm=ata.21736010.0.0.6e462b76wytEry

[17]https://github.com/brahma-adshonor/gohook?spm=ata.21736010.0.0.6e462b76wytEry

[19]https://github.com/bytedance/sonic?spm=ata.21736010.0.0.6e462b76wytEry#compatibility

[20]https://github.com/bytedance/sonic/issues/172?spm=ata.21736010.0.0.6e462b76wytEry

 

作者|趙金鑫(筆話)

本文來自博客園,作者:古道輕風,轉載請註明原文鏈接:https://www.cnblogs.com/88223100/p/Which-is-the-strongest-third-party-package-for-Go-JSON.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • # 前言 最近在開發文件存儲服務,需要符合s3的協議標準,可以直接接入aws-sdk,本文針對sdk發出請求的鑒權信息進行重新組合再簽名驗證有效性,sdk版本如下 ```xml software.amazon.awssdk s3 2.20.45 ``` # 演算法解析 首先對V4版本簽名演算法的數據結構 ...
  • # Rust Web 全棧開發之編寫 WebAssembly 應用 MDN Web Docs: 官網: ## 項目結構 和 功能 **Web App 教師註冊 WebService WebAssembly App 課程管理** ## 什麼是 WebAssembly - WebAssembly 是一種 ...
  • # FileInputStream 和 FileOutputStream ![](https://img2023.cnblogs.com/blog/3008601/202306/3008601-20230604102221520-1382311786.png) - InputStream:位元組輸入流 ...
  • # IO流原理及流的分類 ### 一、Java IO流原理 1. I/O是Input/Output的縮寫,I/O技術是非常實用的技術,用於處理數據傳輸。如讀/寫文件,網路通訊等。 2. Java程式中,對於數據的輸入/輸出操作以”流(stream)“的方式進行。 3. java.io包下提供了各種” ...
  • [模板傳送](https://www.luogu.com.cn/problem/P3369) FHQ-Treap顧名思義就是範浩強大佬設計的一種二叉平衡樹 下麵我們來講一下它的原理和代碼 # 結構體 對於一個節點,我們需要記錄的是 * 對應的值 * 子樹節點數 * 左右孩子編號 * 對應的隨機值 ` ...
  • # 文件的基本使用 ### 一、文件 - **什麼是文件** 文件是保存數據的地方,比如word文檔,txt文件,excel文件……都是文件。即可以保存一張圖片,也可以保持視頻,聲音…… - **文件流** 文件在程式中是以流的形式來操作的 ![文件流](https://img2023.cnblog ...
  • groovy 3.0.7 ### DES加密簡介 加密分為對稱加密和非對稱加密。非對稱加密,加解密使用不同的密鑰,如RSA;對稱加密,加解密使用相同的密鑰,如DES(Data Encryption Standard,即數據加密標準)。相對而言,非對稱加密安全性更高,但是計算過程複雜耗時,一般只應用於 ...
  • # 1.列表切片 前面學習的是如何處理列表的所有數據元素。python還可以處理列表的部分元素,python稱之為切片。 ## 1.1創建切片 創建切片,可指定要使用的第一個數據元素的索引和最後一個數據元素的索引。與range函數一樣,python在到達指定的第二個索引前面的數據元素後停止。比如要輸 ...
一周排行
    -Advertisement-
    Play Games
  • Timer是什麼 Timer 是一種用於創建定期粒度行為的機制。 與標準的 .NET System.Threading.Timer 類相似,Orleans 的 Timer 允許在一段時間後執行特定的操作,或者在特定的時間間隔內重覆執行操作。 它在分散式系統中具有重要作用,特別是在處理需要周期性執行的 ...
  • 前言 相信很多做WPF開發的小伙伴都遇到過表格類的需求,雖然現有的Grid控制項也能實現,但是使用起來的體驗感並不好,比如要實現一個Excel中的表格效果,估計你能想到的第一個方法就是套Border控制項,用這種方法你需要控制每個Border的邊框,並且在一堆Bordr中找到Grid.Row,Grid. ...
  • .NET C#程式啟動閃退,目錄導致的問題 這是第2次踩這個坑了,很小的編程細節,容易忽略,所以寫個博客,分享給大家。 1.第一次坑:是windows 系統把程式運行成服務,找不到配置文件,原因是以服務運行它的工作目錄是在C:\Windows\System32 2.本次坑:WPF桌面程式通過註冊表設 ...
  • 在分散式系統中,數據的持久化是至關重要的一環。 Orleans 7 引入了強大的持久化功能,使得在分散式環境下管理數據變得更加輕鬆和可靠。 本文將介紹什麼是 Orleans 7 的持久化,如何設置它以及相應的代碼示例。 什麼是 Orleans 7 的持久化? Orleans 7 的持久化是指將 Or ...
  • 前言 .NET Feature Management 是一個用於管理應用程式功能的庫,它可以幫助開發人員在應用程式中輕鬆地添加、移除和管理功能。使用 Feature Management,開發人員可以根據不同用戶、環境或其他條件來動態地控制應用程式中的功能。這使得開發人員可以更靈活地管理應用程式的功 ...
  • 在 WPF 應用程式中,拖放操作是實現用戶交互的重要組成部分。通過拖放操作,用戶可以輕鬆地將數據從一個位置移動到另一個位置,或者將控制項從一個容器移動到另一個容器。然而,WPF 中預設的拖放操作可能並不是那麼好用。為瞭解決這個問題,我們可以自定義一個 Panel 來實現更簡單的拖拽操作。 自定義 Pa ...
  • 在實際使用中,由於涉及到不同編程語言之間互相調用,導致C++ 中的OpenCV與C#中的OpenCvSharp 圖像數據在不同編程語言之間難以有效傳遞。在本文中我們將結合OpenCvSharp源碼實現原理,探究兩種數據之間的通信方式。 ...
  • 一、前言 這是一篇搭建許可權管理系統的系列文章。 隨著網路的發展,信息安全對應任何企業來說都越發的重要,而本系列文章將和大家一起一步一步搭建一個全新的許可權管理系統。 說明:由於搭建一個全新的項目過於繁瑣,所有作者將挑選核心代碼和核心思路進行分享。 二、技術選擇 三、開始設計 1、自主搭建vue前端和. ...
  • Csharper中的表達式樹 這節課來瞭解一下表示式樹是什麼? 在C#中,表達式樹是一種數據結構,它可以表示一些代碼塊,如Lambda表達式或查詢表達式。表達式樹使你能夠查看和操作數據,就像你可以查看和操作代碼一樣。它們通常用於創建動態查詢和解析表達式。 一、認識表達式樹 為什麼要這樣說?它和委托有 ...
  • 在使用Django等框架來操作MySQL時,實際上底層還是通過Python來操作的,首先需要安裝一個驅動程式,在Python3中,驅動程式有多種選擇,比如有pymysql以及mysqlclient等。使用pip命令安裝mysqlclient失敗應如何解決? 安裝的python版本說明 機器同時安裝了 ...