diff --git a/README.md b/README.md index 9a90ad2..e3c464d 100644 --- a/README.md +++ b/README.md @@ -6,30 +6,39 @@ A blazingly fast JSON serializing & deserializing library, accelerated by JI ## Benchmarks For all sizes of json and all scenes of usage, Sonic performs almost best. -- Small (400B, 11 keys, 3 levels) +- [Small](https://github.com/bytedance/sonic/blob/main/testdata/small.go) (400B, 11 keys, 3 levels) ![small benchmarks](bench-400B.png) -- Medium (110KB, 300+ keys, 3 levels, with many quoted-json values) -![medium benchmarks](bench-110KB.png) -- Large (550KB, 10000+ key, 6 levels) +- [Large](https://github.com/bytedance/sonic/blob/main/testdata/twitterescaped.json) (550KB, 10000+ key, 6 levels) ![large benchmarks](bench-550KB.png) -For a 13KB [TwitterJson](https://github.com/bytedance/sonic/blob/main/decoder/testdata_test.go#L19)(cpu i9-9880H, goarch amd64), Sonic is **1.5x** faster than [json-iterator](https://github.com/json-iterator/go) in decoding, **2.5x** faster in encoding. +For a 13KB [TwitterJson](https://github.com/bytedance/sonic/blob/main/decoder/testdata_test.go#L19), Sonic is **1.5x** faster than [json-iterator](https://github.com/json-iterator/go) in decoding, **2.5x** faster in encoding. ```powershell -BenchmarkDecoder_Generic_Sonic-16 10000 54309 ns/op 240.01 MB/s 46149 B/op 303 allocs/op -BenchmarkDecoder_Generic_StdLib-16 10000 135268 ns/op 96.36 MB/s 50899 B/op 772 allocs/op -BenchmarkDecoder_Generic_JsonIter-16 10000 96701 ns/op 134.80 MB/s 55791 B/op 1068 allocs/op -BenchmarkDecoder_Binding_Sonic-16 10000 29478 ns/op 442.20 MB/s 26062 B/op 34 allocs/op -BenchmarkDecoder_Binding_StdLib-16 10000 119348 ns/op 109.22 MB/s 10560 B/op 207 allocs/op -BenchmarkDecoder_Binding_JsonIter-16 10000 37646 ns/op 346.25 MB/s 14673 B/op 385 allocs/op -BenchmarkEncoder_Generic_Sonic-16 10000 25894 ns/op 503.39 MB/s 19096 B/op 42 allocs/op -BenchmarkEncoder_Generic_JsonIter-16 10000 50275 ns/op 259.27 MB/s 13432 B/op 77 allocs/op -BenchmarkEncoder_Generic_StdLib-16 10000 154901 ns/op 84.15 MB/s 48173 B/op 827 allocs/op -BenchmarkEncoder_Binding_Sonic-16 10000 7373 ns/op 1768.04 MB/s 13861 B/op 4 allocs/op -BenchmarkEncoder_Binding_JsonIter-16 10000 23223 ns/op 561.31 MB/s 9489 B/op 2 allocs/op -BenchmarkEncoder_Binding_StdLib-16 10000 19512 ns/op 668.07 MB/s 9477 B/op 1 allocs/op +goos: darwin +goarch: amd64 +pkg: github.com/bytedance/sonic/encoder +cpu: Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz +BenchmarkEncoder_Generic_Sonic-16 100000 24174 ns/op 539.22 MB/s 17757 B/op 42 allocs/op +BenchmarkEncoder_Generic_JsonIter-16 100000 44613 ns/op 292.18 MB/s 13433 B/op 77 allocs/op +BenchmarkEncoder_Generic_GoJson-16 100000 87898 ns/op 148.30 MB/s 13234 B/op 39 allocs/op +BenchmarkEncoder_Generic_StdLib-16 100000 133512 ns/op 97.63 MB/s 48177 B/op 827 allocs/op + +BenchmarkEncoder_Binding_Sonic-16 100000 6058 ns/op 2151.73 MB/s 13481 B/op 4 allocs/op +BenchmarkEncoder_Binding_JsonIter-16 100000 21223 ns/op 614.20 MB/s 9488 B/op 2 allocs/op +BenchmarkEncoder_Binding_GoJson-16 100000 10186 ns/op 1279.74 MB/s 9480 B/op 1 allocs/op +BenchmarkEncoder_Binding_StdLib-16 100000 17741 ns/op 734.75 MB/s 9479 B/op 1 allocs/op + +BenchmarkDecoder_Generic_Sonic-16 100000 53344 ns/op 244.36 MB/s 50158 B/op 313 allocs/op +BenchmarkDecoder_Generic_StdLib-16 100000 141006 ns/op 92.44 MB/s 50898 B/op 772 allocs/op +BenchmarkDecoder_Generic_JsonIter-16 100000 106386 ns/op 122.53 MB/s 55785 B/op 1068 allocs/op +BenchmarkDecoder_Generic_GoJson-16 100000 107184 ns/op 121.61 MB/s 65678 B/op 944 allocs/op + +BenchmarkDecoder_Binding_Sonic-16 100000 30039 ns/op 433.94 MB/s 25259 B/op 34 allocs/op +BenchmarkDecoder_Binding_StdLib-16 100000 131088 ns/op 99.44 MB/s 10560 B/op 207 allocs/op +BenchmarkDecoder_Binding_JsonIter-16 100000 37988 ns/op 343.13 MB/s 14674 B/op 385 allocs/op +BenchmarkDecoder_Binding_GoJson-16 100000 33741 ns/op 386.33 MB/s 22047 B/op 49 allocs/op ``` -More detail see [ast/search_test.go](https://github.com/bytedance/sonic/blob/main/ast/search_test.go), [decoder/decoder_test.go](https://github.com/bytedance/sonic/blob/main/decoder/decoder_test.go), [encoder/encoder_test.go](https://github.com/bytedance/sonic/blob/main/encoder/encoder_test.go), +More detail see [decoder/decoder_test.go](https://github.com/bytedance/sonic/blob/main/decoder/decoder_test.go), [encoder/encoder_test.go](https://github.com/bytedance/sonic/blob/main/encoder/encoder_test.go), [ast/search_test.go](https://github.com/bytedance/sonic/blob/main/ast/search_test.go), [ast/parser_test.go](https://github.com/bytedance/sonic/blob/main/ast/parser_test.go) ## Usage @@ -119,7 +128,7 @@ For alignment to encoding/json, we provide API to pass `[]byte` as arguement, bu ```go import "github.com/bytedance/sonic" -root, err := sonic.GetByString(_TwitterJson, "statuses", 3, "user") +root, err := sonic.GetFromString(_TwitterJson, "statuses", 3, "user") a = root.GetByPath( "entities","description") b = root.GetByPath( "entities","url") c = root.GetByPath( "created_at") @@ -130,7 +139,7 @@ In most cases of fully-load generic json, `Unmarshal()` performs better than `as ```go import "github.com/bytedance/sonic" -node, err := sonic.GetByString(_TwitterJson, "statuses", 3, "user") +node, err := sonic.GetFromString(_TwitterJson, "statuses", 3, "user") var user interface{} err = sonic.UnmarshalString(node.Raw(), &user) ``` diff --git a/bench-110KB.png b/bench-110KB.png deleted file mode 100644 index 87a3622..0000000 Binary files a/bench-110KB.png and /dev/null differ diff --git a/bench-400B.png b/bench-400B.png index 23123b9..e97f76a 100644 Binary files a/bench-400B.png and b/bench-400B.png differ diff --git a/bench-550KB.png b/bench-550KB.png index 4c2236f..514f160 100644 Binary files a/bench-550KB.png and b/bench-550KB.png differ diff --git a/decoder/decoder_test.go b/decoder/decoder_test.go index 01c82c0..af6e8d7 100644 --- a/decoder/decoder_test.go +++ b/decoder/decoder_test.go @@ -24,6 +24,7 @@ import ( `github.com/json-iterator/go` `github.com/stretchr/testify/assert` `github.com/stretchr/testify/require` + gojson `github.com/goccy/go-json` ) var _BindingValue TwitterStruct @@ -111,6 +112,18 @@ func BenchmarkDecoder_Generic_JsonIter(b *testing.B) { } } +func BenchmarkDecoder_Generic_GoJson(b *testing.B) { + var w interface{} + m := []byte(TwitterJson) + _ = gojson.Unmarshal(m, &w) + b.SetBytes(int64(len(TwitterJson))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + var v interface{} + _ = gojson.Unmarshal(m, &v) + } +} + func BenchmarkDecoder_Binding_Sonic(b *testing.B) { var w TwitterStruct _, _ = decode(TwitterJson, &w) @@ -146,6 +159,18 @@ func BenchmarkDecoder_Binding_JsonIter(b *testing.B) { } } +func BenchmarkDecoder_Binding_GoJson(b *testing.B) { + var w TwitterStruct + m := []byte(TwitterJson) + _ = gojson.Unmarshal(m, &w) + b.SetBytes(int64(len(TwitterJson))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + var v TwitterStruct + _ = gojson.Unmarshal(m, &v) + } +} + func BenchmarkDecoder_Parallel_Generic_Sonic(b *testing.B) { var w interface{} _, _ = decode(TwitterJson, &w) @@ -187,6 +212,20 @@ func BenchmarkDecoder_Parallel_Generic_JsonIter(b *testing.B) { }) } +func BenchmarkDecoder_Parallel_Generic_GoJson(b *testing.B) { + var w interface{} + m := []byte(TwitterJson) + _ = gojson.Unmarshal(m, &w) + b.SetBytes(int64(len(TwitterJson))) + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + var v interface{} + _ = gojson.Unmarshal(m, &v) + } + }) +} + func BenchmarkDecoder_Parallel_Binding_Sonic(b *testing.B) { var w TwitterStruct _, _ = decode(TwitterJson, &w) @@ -227,3 +266,18 @@ func BenchmarkDecoder_Parallel_Binding_JsonIter(b *testing.B) { } }) } + +func BenchmarkDecoder_Parallel_Binding_GoJson(b *testing.B) { + var w TwitterStruct + m := []byte(TwitterJson) + _ = gojson.Unmarshal(m, &w) + b.SetBytes(int64(len(TwitterJson))) + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + var v TwitterStruct + _ = gojson.Unmarshal(m, &v) + } + }) +} + diff --git a/encoder/encoder_test.go b/encoder/encoder_test.go index 1778d3f..0da0434 100644 --- a/encoder/encoder_test.go +++ b/encoder/encoder_test.go @@ -22,6 +22,7 @@ import ( `testing` `github.com/json-iterator/go` + gojson `github.com/goccy/go-json` `github.com/stretchr/testify/assert` ) @@ -128,6 +129,15 @@ func BenchmarkEncoder_Generic_JsonIter(b *testing.B) { } } +func BenchmarkEncoder_Generic_GoJson(b *testing.B) { + _, _ = gojson.Marshal(_GenericValue) + b.SetBytes(int64(len(TwitterJson))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = gojson.Marshal(_GenericValue) + } +} + func BenchmarkEncoder_Generic_StdLib(b *testing.B) { _, _ = json.Marshal(_GenericValue) b.SetBytes(int64(len(TwitterJson))) @@ -155,6 +165,15 @@ func BenchmarkEncoder_Binding_JsonIter(b *testing.B) { } } +func BenchmarkEncoder_Binding_GoJson(b *testing.B) { + _, _ = gojson.Marshal(&_BindingValue) + b.SetBytes(int64(len(TwitterJson))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = gojson.Marshal(&_BindingValue) + } +} + func BenchmarkEncoder_Binding_StdLib(b *testing.B) { _, _ = json.Marshal(&_BindingValue) b.SetBytes(int64(len(TwitterJson))) @@ -186,6 +205,17 @@ func BenchmarkEncoder_Parallel_Generic_JsonIter(b *testing.B) { }) } +func BenchmarkEncoder_Parallel_Generic_GoJson(b *testing.B) { + _, _ = gojson.Marshal(_GenericValue) + b.SetBytes(int64(len(TwitterJson))) + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _, _ = gojson.Marshal(_GenericValue) + } + }) +} + func BenchmarkEncoder_Parallel_Generic_StdLib(b *testing.B) { _, _ = json.Marshal(_GenericValue) b.SetBytes(int64(len(TwitterJson))) @@ -219,6 +249,17 @@ func BenchmarkEncoder_Parallel_Binding_JsonIter(b *testing.B) { }) } +func BenchmarkEncoder_Parallel_Binding_GoJson(b *testing.B) { + _, _ = gojson.Marshal(&_BindingValue) + b.SetBytes(int64(len(TwitterJson))) + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _, _ = gojson.Marshal(&_BindingValue) + } + }) +} + func BenchmarkEncoder_Parallel_Binding_StdLib(b *testing.B) { _, _ = json.Marshal(&_BindingValue) b.SetBytes(int64(len(TwitterJson))) diff --git a/go.mod b/go.mod index d1c0696..ba2ce15 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.15 require ( github.com/chenzhuoyu/base64x v0.0.0-20210528162528-3c6c11c43ee5 github.com/davecgh/go-spew v1.1.1 + github.com/goccy/go-json v0.7.1 // indirect github.com/json-iterator/go v1.1.10 github.com/klauspost/cpuid/v2 v2.0.6 github.com/stretchr/testify v1.7.0 diff --git a/go.sum b/go.sum index 7a0b19e..36e8d53 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,8 @@ github.com/chenzhuoyu/base64x v0.0.0-20210528162528-3c6c11c43ee5/go.mod h1:NfDzX github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/goccy/go-json v0.7.1 h1:VMhnh5gcc8De8f6m2DLvSqY1x8Jwl3btet+EqMP0QNs= +github.com/goccy/go-json v0.7.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= diff --git a/testdata/small.go b/testdata/small.go new file mode 100644 index 0000000..bffa2ac --- /dev/null +++ b/testdata/small.go @@ -0,0 +1,64 @@ + +/* + * Copyright 2021 ByteDance Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package testdata + +// ffjson: skip +// easyjson:skip +type Book struct { + BookId int `json:"id"` + BookIds []int `json:"ids"` + Title string `json:"title"` + Titles []string `json:"titles"` + Price float64 `json:"price"` + Prices []float64 `json:"prices"` + Hot bool `json:"hot"` + Hots []bool `json:"hots"` + Author Author `json:"author"` + Authors []Author `json:"authors"` + Weights []int `json:"weights"` +} + +// ffjson: skip +// easyjson:skip +type Author struct { + Name string `json:"name"` + Age int `json:"age"` + Male bool `json:"male"` +} + +var book = Book{ + BookId: 12125925, + BookIds: []int{-2147483648, 2147483647}, + Title: "未来简史-从智人到智神", + Titles: []string{"hello", "world"}, + Price: 40.8, + Prices: []float64{-0.1, 0.1}, + Hot: true, + Hots: []bool{true, true, true}, + Author: author, + Authors: []Author{author, author, author}, + Weights: nil, +} + +var author = Author{ + Name: "json", + Age: 99, + Male: true, +} + +var data = []byte(`{"id":12125925,"ids":[-2147483648,2147483647],"title":"未来简史-从智人到智神","titles":["hello","world"],"price":40.8,"prices":[-0.1,0.1],"hot":true,"hots":[true,true,true],"author":{"name":"json","age":99,"male":true},"authors":[{"name":"json","age":99,"male":true},{"name":"json","age":99,"male":true},{"name":"json","age":99,"male":true}],"weights":[]}`) \ No newline at end of file