Skip to content

Commit e7c7154

Browse files
charlieviethqingyang-hu
authored andcommitted
GODRIVER-2914 bson: improve marshal/unmarshal by replacing decoder/encoder caches with sync.Map. (#1313)
1 parent f7134aa commit e7c7154

File tree

8 files changed

+662
-191
lines changed

8 files changed

+662
-191
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(codeBSON)))
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(codeBSON)))
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/bsoncodec/codec_cache.go

+166
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
// Copyright (C) MongoDB, Inc. 2017-present.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"); you may
4+
// not use this file except in compliance with the License. You may obtain
5+
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
7+
package bsoncodec
8+
9+
import (
10+
"reflect"
11+
"sync"
12+
"sync/atomic"
13+
)
14+
15+
// Runtime check that the kind encoder and decoder caches can store any valid
16+
// reflect.Kind constant.
17+
func init() {
18+
if s := reflect.Kind(len(kindEncoderCache{}.entries)).String(); s != "kind27" {
19+
panic("The capacity of kindEncoderCache is too small.\n" +
20+
"This is due to a new type being added to reflect.Kind.")
21+
}
22+
}
23+
24+
// statically assert array size
25+
var _ = (kindEncoderCache{}).entries[reflect.UnsafePointer]
26+
var _ = (kindDecoderCache{}).entries[reflect.UnsafePointer]
27+
28+
type typeEncoderCache struct {
29+
cache sync.Map // map[reflect.Type]ValueEncoder
30+
}
31+
32+
func (c *typeEncoderCache) Store(rt reflect.Type, enc ValueEncoder) {
33+
c.cache.Store(rt, enc)
34+
}
35+
36+
func (c *typeEncoderCache) Load(rt reflect.Type) (ValueEncoder, bool) {
37+
if v, _ := c.cache.Load(rt); v != nil {
38+
return v.(ValueEncoder), true
39+
}
40+
return nil, false
41+
}
42+
43+
func (c *typeEncoderCache) LoadOrStore(rt reflect.Type, enc ValueEncoder) ValueEncoder {
44+
if v, loaded := c.cache.LoadOrStore(rt, enc); loaded {
45+
enc = v.(ValueEncoder)
46+
}
47+
return enc
48+
}
49+
50+
func (c *typeEncoderCache) Clone() *typeEncoderCache {
51+
cc := new(typeEncoderCache)
52+
c.cache.Range(func(k, v interface{}) bool {
53+
if k != nil && v != nil {
54+
cc.cache.Store(k, v)
55+
}
56+
return true
57+
})
58+
return cc
59+
}
60+
61+
type typeDecoderCache struct {
62+
cache sync.Map // map[reflect.Type]ValueDecoder
63+
}
64+
65+
func (c *typeDecoderCache) Store(rt reflect.Type, dec ValueDecoder) {
66+
c.cache.Store(rt, dec)
67+
}
68+
69+
func (c *typeDecoderCache) Load(rt reflect.Type) (ValueDecoder, bool) {
70+
if v, _ := c.cache.Load(rt); v != nil {
71+
return v.(ValueDecoder), true
72+
}
73+
return nil, false
74+
}
75+
76+
func (c *typeDecoderCache) LoadOrStore(rt reflect.Type, dec ValueDecoder) ValueDecoder {
77+
if v, loaded := c.cache.LoadOrStore(rt, dec); loaded {
78+
dec = v.(ValueDecoder)
79+
}
80+
return dec
81+
}
82+
83+
func (c *typeDecoderCache) Clone() *typeDecoderCache {
84+
cc := new(typeDecoderCache)
85+
c.cache.Range(func(k, v interface{}) bool {
86+
if k != nil && v != nil {
87+
cc.cache.Store(k, v)
88+
}
89+
return true
90+
})
91+
return cc
92+
}
93+
94+
// atomic.Value requires that all calls to Store() have the same concrete type
95+
// so we wrap the ValueEncoder with a kindEncoderCacheEntry to ensure the type
96+
// is always the same (since different concrete types may implement the
97+
// ValueEncoder interface).
98+
type kindEncoderCacheEntry struct {
99+
enc ValueEncoder
100+
}
101+
102+
type kindEncoderCache struct {
103+
entries [reflect.UnsafePointer + 1]atomic.Value // *kindEncoderCacheEntry
104+
}
105+
106+
func (c *kindEncoderCache) Store(rt reflect.Kind, enc ValueEncoder) {
107+
if enc != nil && rt < reflect.Kind(len(c.entries)) {
108+
c.entries[rt].Store(&kindEncoderCacheEntry{enc: enc})
109+
}
110+
}
111+
112+
func (c *kindEncoderCache) Load(rt reflect.Kind) (ValueEncoder, bool) {
113+
if rt < reflect.Kind(len(c.entries)) {
114+
if ent, ok := c.entries[rt].Load().(*kindEncoderCacheEntry); ok {
115+
return ent.enc, ent.enc != nil
116+
}
117+
}
118+
return nil, false
119+
}
120+
121+
func (c *kindEncoderCache) Clone() *kindEncoderCache {
122+
cc := new(kindEncoderCache)
123+
for i, v := range c.entries {
124+
if val := v.Load(); val != nil {
125+
cc.entries[i].Store(val)
126+
}
127+
}
128+
return cc
129+
}
130+
131+
// atomic.Value requires that all calls to Store() have the same concrete type
132+
// so we wrap the ValueDecoder with a kindDecoderCacheEntry to ensure the type
133+
// is always the same (since different concrete types may implement the
134+
// ValueDecoder interface).
135+
type kindDecoderCacheEntry struct {
136+
dec ValueDecoder
137+
}
138+
139+
type kindDecoderCache struct {
140+
entries [reflect.UnsafePointer + 1]atomic.Value // *kindDecoderCacheEntry
141+
}
142+
143+
func (c *kindDecoderCache) Store(rt reflect.Kind, dec ValueDecoder) {
144+
if rt < reflect.Kind(len(c.entries)) {
145+
c.entries[rt].Store(&kindDecoderCacheEntry{dec: dec})
146+
}
147+
}
148+
149+
func (c *kindDecoderCache) Load(rt reflect.Kind) (ValueDecoder, bool) {
150+
if rt < reflect.Kind(len(c.entries)) {
151+
if ent, ok := c.entries[rt].Load().(*kindDecoderCacheEntry); ok {
152+
return ent.dec, ent.dec != nil
153+
}
154+
}
155+
return nil, false
156+
}
157+
158+
func (c *kindDecoderCache) Clone() *kindDecoderCache {
159+
cc := new(kindDecoderCache)
160+
for i, v := range c.entries {
161+
if val := v.Load(); val != nil {
162+
cc.entries[i].Store(val)
163+
}
164+
}
165+
return cc
166+
}

0 commit comments

Comments
 (0)