Skip to content

Commit db543ec

Browse files
committed
bson: add BenchmarkCode{Marshal,Unmarshal} benchmarks from encoding/json
These benchmarks do a good job of identifying bottlenecks in the bson encoding/decoding logic.
1 parent 8489898 commit db543ec

File tree

2 files changed

+142
-0
lines changed

2 files changed

+142
-0
lines changed

bson/benchmark_test.go

+142
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@
77
package bson
88

99
import (
10+
"bytes"
1011
"compress/gzip"
1112
"encoding/json"
1213
"fmt"
14+
"io"
1315
"io/ioutil"
1416
"os"
1517
"path"
18+
"sync"
1619
"testing"
1720
)
1821

@@ -129,10 +132,20 @@ var nestedInstance = nestedtest1{
129132

130133
const extendedBSONDir = "../testdata/extended_bson"
131134

135+
var (
136+
extJSONFiles map[string]map[string]interface{}
137+
extJSONFilesMu sync.Mutex
138+
)
139+
132140
// readExtJSONFile reads the GZIP-compressed extended JSON document from the given filename in the
133141
// "extended BSON" test data directory (../testdata/extended_bson) and returns it as a
134142
// map[string]interface{}. It panics on any errors.
135143
func readExtJSONFile(filename string) map[string]interface{} {
144+
extJSONFilesMu.Lock()
145+
defer extJSONFilesMu.Unlock()
146+
if v, ok := extJSONFiles[filename]; ok {
147+
return v
148+
}
136149
filePath := path.Join(extendedBSONDir, filename)
137150
file, err := os.Open(filePath)
138151
if err != nil {
@@ -161,6 +174,10 @@ func readExtJSONFile(filename string) map[string]interface{} {
161174
panic(fmt.Sprintf("error unmarshalling extended JSON: %s", err))
162175
}
163176

177+
if extJSONFiles == nil {
178+
extJSONFiles = make(map[string]map[string]interface{})
179+
}
180+
extJSONFiles[filename] = v
164181
return v
165182
}
166183

@@ -305,3 +322,128 @@ func BenchmarkUnmarshal(b *testing.B) {
305322
})
306323
}
307324
}
325+
326+
// The following benchmarks are copied from the Go standard library's
327+
// encoding/json package.
328+
329+
type codeResponse struct {
330+
Tree *codeNode `json:"tree"`
331+
Username string `json:"username"`
332+
}
333+
334+
type codeNode struct {
335+
Name string `json:"name"`
336+
Kids []*codeNode `json:"kids"`
337+
CLWeight float64 `json:"cl_weight"`
338+
Touches int `json:"touches"`
339+
MinT int64 `json:"min_t"`
340+
MaxT int64 `json:"max_t"`
341+
MeanT int64 `json:"mean_t"`
342+
}
343+
344+
var codeJSON []byte
345+
var codeBSON []byte
346+
var codeStruct codeResponse
347+
348+
func codeInit() {
349+
f, err := os.Open("testdata/code.json.gz")
350+
if err != nil {
351+
panic(err)
352+
}
353+
defer f.Close()
354+
gz, err := gzip.NewReader(f)
355+
if err != nil {
356+
panic(err)
357+
}
358+
data, err := io.ReadAll(gz)
359+
if err != nil {
360+
panic(err)
361+
}
362+
363+
codeJSON = data
364+
365+
if err := json.Unmarshal(codeJSON, &codeStruct); err != nil {
366+
panic("json.Unmarshal code.json: " + err.Error())
367+
}
368+
369+
if data, err = json.Marshal(&codeStruct); err != nil {
370+
panic("json.Marshal code.json: " + err.Error())
371+
}
372+
373+
if codeBSON, err = Marshal(&codeStruct); err != nil {
374+
panic("Marshal code.json: " + err.Error())
375+
}
376+
377+
if !bytes.Equal(data, codeJSON) {
378+
println("different lengths", len(data), len(codeJSON))
379+
for i := 0; i < len(data) && i < len(codeJSON); i++ {
380+
if data[i] != codeJSON[i] {
381+
println("re-marshal: changed at byte", i)
382+
println("orig: ", string(codeJSON[i-10:i+10]))
383+
println("new: ", string(data[i-10:i+10]))
384+
break
385+
}
386+
}
387+
panic("re-marshal code.json: different result")
388+
}
389+
}
390+
391+
func BenchmarkCodeUnmarshal(b *testing.B) {
392+
b.ReportAllocs()
393+
if codeJSON == nil {
394+
b.StopTimer()
395+
codeInit()
396+
b.StartTimer()
397+
}
398+
b.Run("BSON", func(b *testing.B) {
399+
b.RunParallel(func(pb *testing.PB) {
400+
for pb.Next() {
401+
var r codeResponse
402+
if err := Unmarshal(codeBSON, &r); err != nil {
403+
b.Fatal("Unmarshal:", err)
404+
}
405+
}
406+
})
407+
b.SetBytes(int64(len(codeJSON)))
408+
})
409+
b.Run("JSON", func(b *testing.B) {
410+
b.RunParallel(func(pb *testing.PB) {
411+
for pb.Next() {
412+
var r codeResponse
413+
if err := json.Unmarshal(codeJSON, &r); err != nil {
414+
b.Fatal("json.Unmarshal:", err)
415+
}
416+
}
417+
})
418+
b.SetBytes(int64(len(codeJSON)))
419+
})
420+
}
421+
422+
func BenchmarkCodeMarshal(b *testing.B) {
423+
b.ReportAllocs()
424+
if codeJSON == nil {
425+
b.StopTimer()
426+
codeInit()
427+
b.StartTimer()
428+
}
429+
b.Run("BSON", func(b *testing.B) {
430+
b.RunParallel(func(pb *testing.PB) {
431+
for pb.Next() {
432+
if _, err := Marshal(&codeStruct); err != nil {
433+
b.Fatal("Marshal:", err)
434+
}
435+
}
436+
})
437+
b.SetBytes(int64(len(codeJSON)))
438+
})
439+
b.Run("JSON", func(b *testing.B) {
440+
b.RunParallel(func(pb *testing.PB) {
441+
for pb.Next() {
442+
if _, err := json.Marshal(&codeStruct); err != nil {
443+
b.Fatal("json.Marshal:", err)
444+
}
445+
}
446+
})
447+
b.SetBytes(int64(len(codeJSON)))
448+
})
449+
}

bson/testdata/code.json.gz

118 KB
Binary file not shown.

0 commit comments

Comments
 (0)