Skip to content

Commit 8d3e383

Browse files
committed
Revert "GODRIVER-2914 bsoncodec/bsonrw: eliminate encoding allocations (mongodb#1323)"
This reverts commit 5a5a231.
1 parent 01cc2a2 commit 8d3e383

File tree

8 files changed

+84
-119
lines changed

8 files changed

+84
-119
lines changed

bson/bsoncodec/slice_codec.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ func (sc SliceCodec) EncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val re
6262
}
6363

6464
// If we have a []primitive.E we want to treat it as a document instead of as an array.
65-
if val.Type() == tD || val.Type().ConvertibleTo(tD) {
65+
if val.Type().ConvertibleTo(tD) {
6666
d := val.Convert(tD).Interface().(primitive.D)
6767

6868
dw, err := vw.WriteDocument()

bson/bsoncodec/struct_codec.go

Lines changed: 49 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -190,14 +190,15 @@ func (sc *StructCodec) EncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val
190190
encoder := desc.encoder
191191

192192
var zero bool
193+
rvInterface := rv.Interface()
193194
if cz, ok := encoder.(CodecZeroer); ok {
194-
zero = cz.IsTypeZero(rv.Interface())
195+
zero = cz.IsTypeZero(rvInterface)
195196
} else if rv.Kind() == reflect.Interface {
196197
// isZero will not treat an interface rv as an interface, so we need to check for the
197198
// zero interface separately.
198199
zero = rv.IsNil()
199200
} else {
200-
zero = isZero(rv, sc.EncodeOmitDefaultStruct || ec.omitZeroStruct)
201+
zero = isZero(rvInterface, sc.EncodeOmitDefaultStruct || ec.omitZeroStruct)
201202
}
202203
if desc.omitEmpty && zero {
203204
continue
@@ -391,32 +392,56 @@ func (sc *StructCodec) DecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val
391392
return nil
392393
}
393394

394-
func isZero(v reflect.Value, omitZeroStruct bool) bool {
395-
kind := v.Kind()
396-
if (kind != reflect.Ptr || !v.IsNil()) && v.Type().Implements(tZeroer) {
397-
return v.Interface().(Zeroer).IsZero()
395+
func isZero(i interface{}, omitZeroStruct bool) bool {
396+
v := reflect.ValueOf(i)
397+
398+
// check the value validity
399+
if !v.IsValid() {
400+
return true
398401
}
399-
if kind == reflect.Struct {
402+
403+
if z, ok := v.Interface().(Zeroer); ok && (v.Kind() != reflect.Ptr || !v.IsNil()) {
404+
return z.IsZero()
405+
}
406+
407+
switch v.Kind() {
408+
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
409+
return v.Len() == 0
410+
case reflect.Bool:
411+
return !v.Bool()
412+
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
413+
return v.Int() == 0
414+
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
415+
return v.Uint() == 0
416+
case reflect.Float32, reflect.Float64:
417+
return v.Float() == 0
418+
case reflect.Interface, reflect.Ptr:
419+
return v.IsNil()
420+
case reflect.Struct:
400421
if !omitZeroStruct {
401422
return false
402423
}
424+
425+
// TODO(GODRIVER-2820): Update the logic to be able to handle private struct fields.
426+
// TODO Use condition "reflect.Zero(v.Type()).Equal(v)" instead.
427+
403428
vt := v.Type()
404429
if vt == tTime {
405430
return v.Interface().(time.Time).IsZero()
406431
}
407-
numField := vt.NumField()
408-
for i := 0; i < numField; i++ {
409-
ff := vt.Field(i)
410-
if ff.PkgPath != "" && !ff.Anonymous {
432+
for i := 0; i < v.NumField(); i++ {
433+
if vt.Field(i).PkgPath != "" && !vt.Field(i).Anonymous {
411434
continue // Private field
412435
}
413-
if !isZero(v.Field(i), omitZeroStruct) {
436+
fld := v.Field(i)
437+
if !isZero(fld.Interface(), omitZeroStruct) {
414438
return false
415439
}
416440
}
417441
return true
418442
}
419-
return !v.IsValid() || v.IsZero()
443+
444+
return false
420445
}
421446

422447
type structDescription struct {
@@ -683,21 +708,21 @@ func getInlineField(val reflect.Value, index []int) (reflect.Value, error) {
683708

684709
// DeepZero returns recursive zero object
685710
func deepZero(st reflect.Type) (result reflect.Value) {
686-
if st.Kind() == reflect.Struct {
687-
numField := st.NumField()
688-
for i := 0; i < numField; i++ {
689-
if result == emptyValue {
690-
result = reflect.Indirect(reflect.New(st))
691-
}
692-
f := result.Field(i)
693-
if f.CanInterface() {
694-
if f.Type().Kind() == reflect.Struct {
695-
result.Field(i).Set(recursivePointerTo(deepZero(f.Type().Elem())))
711+
result = reflect.Indirect(reflect.New(st))
712+
713+
if result.Kind() == reflect.Struct {
714+
for i := 0; i < result.NumField(); i++ {
715+
if f := result.Field(i); f.Kind() == reflect.Ptr {
716+
if f.CanInterface() {
717+
if ft := reflect.TypeOf(f.Interface()); ft.Elem().Kind() == reflect.Struct {
718+
result.Field(i).Set(recursivePointerTo(deepZero(ft.Elem())))
719+
}
696720
}
697721
}
698722
}
699723
}
700-
return result
724+
725+
return
701726
}
702727

703728
// recursivePointerTo calls reflect.New(v.Type) but recursively for its fields inside

bson/bsoncodec/struct_codec_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
package bsoncodec
88

99
import (
10-
"reflect"
1110
"testing"
1211
"time"
1312

@@ -148,7 +147,7 @@ func TestIsZero(t *testing.T) {
148147
t.Run(tc.description, func(t *testing.T) {
149148
t.Parallel()
150149

151-
got := isZero(reflect.ValueOf(tc.value), tc.omitZeroStruct)
150+
got := isZero(tc.value, tc.omitZeroStruct)
152151
assert.Equal(t, tc.want, got, "expected and actual isZero return are different")
153152
})
154153
}

bson/bsoncodec/types.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ var tValueUnmarshaler = reflect.TypeOf((*ValueUnmarshaler)(nil)).Elem()
3434
var tMarshaler = reflect.TypeOf((*Marshaler)(nil)).Elem()
3535
var tUnmarshaler = reflect.TypeOf((*Unmarshaler)(nil)).Elem()
3636
var tProxy = reflect.TypeOf((*Proxy)(nil)).Elem()
37-
var tZeroer = reflect.TypeOf((*Zeroer)(nil)).Elem()
3837

3938
var tBinary = reflect.TypeOf(primitive.Binary{})
4039
var tUndefined = reflect.TypeOf(primitive.Undefined{})

bson/bsonrw/copier.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ func (c Copier) AppendDocumentBytes(dst []byte, src ValueReader) ([]byte, error)
193193
}
194194

195195
vw := vwPool.Get().(*valueWriter)
196-
defer putValueWriter(vw)
196+
defer vwPool.Put(vw)
197197

198198
vw.reset(dst)
199199

@@ -213,7 +213,7 @@ func (c Copier) AppendArrayBytes(dst []byte, src ValueReader) ([]byte, error) {
213213
}
214214

215215
vw := vwPool.Get().(*valueWriter)
216-
defer putValueWriter(vw)
216+
defer vwPool.Put(vw)
217217

218218
vw.reset(dst)
219219

@@ -258,7 +258,7 @@ func (c Copier) AppendValueBytes(dst []byte, src ValueReader) (bsontype.Type, []
258258
}
259259

260260
vw := vwPool.Get().(*valueWriter)
261-
defer putValueWriter(vw)
261+
defer vwPool.Put(vw)
262262

263263
start := len(dst)
264264

bson/bsonrw/value_reader.go

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -739,7 +739,8 @@ func (vr *valueReader) ReadValue() (ValueReader, error) {
739739
return nil, ErrEOA
740740
}
741741

742-
if err := vr.skipCString(); err != nil {
742+
_, err = vr.readCString()
743+
if err != nil {
743744
return nil, err
744745
}
745746

@@ -793,15 +794,6 @@ func (vr *valueReader) readByte() (byte, error) {
793794
return vr.d[vr.offset-1], nil
794795
}
795796

796-
func (vr *valueReader) skipCString() error {
797-
idx := bytes.IndexByte(vr.d[vr.offset:], 0x00)
798-
if idx < 0 {
799-
return io.EOF
800-
}
801-
vr.offset += int64(idx) + 1
802-
return nil
803-
}
804-
805797
func (vr *valueReader) readCString() (string, error) {
806798
idx := bytes.IndexByte(vr.d[vr.offset:], 0x00)
807799
if idx < 0 {

bson/bsonrw/value_writer.go

Lines changed: 25 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,6 @@ var vwPool = sync.Pool{
2828
},
2929
}
3030

31-
func putValueWriter(vw *valueWriter) {
32-
if vw != nil {
33-
vw.w = nil // don't leak the writer
34-
vwPool.Put(vw)
35-
}
36-
}
37-
3831
// BSONValueWriterPool is a pool for BSON ValueWriters.
3932
//
4033
// Deprecated: BSONValueWriterPool will not be supported in Go Driver 2.0.
@@ -156,21 +149,32 @@ type valueWriter struct {
156149
}
157150

158151
func (vw *valueWriter) advanceFrame() {
159-
vw.frame++
160-
if vw.frame >= int64(len(vw.stack)) {
161-
vw.stack = append(vw.stack, vwState{})
152+
if vw.frame+1 >= int64(len(vw.stack)) { // We need to grow the stack
153+
length := len(vw.stack)
154+
if length+1 >= cap(vw.stack) {
155+
// double it
156+
buf := make([]vwState, 2*cap(vw.stack)+1)
157+
copy(buf, vw.stack)
158+
vw.stack = buf
159+
}
160+
vw.stack = vw.stack[:length+1]
162161
}
162+
vw.frame++
163163
}
164164

165165
func (vw *valueWriter) push(m mode) {
166166
vw.advanceFrame()
167167

168168
// Clean the stack
169-
vw.stack[vw.frame] = vwState{mode: m}
169+
vw.stack[vw.frame].mode = m
170+
vw.stack[vw.frame].key = ""
171+
vw.stack[vw.frame].arrkey = 0
172+
vw.stack[vw.frame].start = 0
170173

174+
vw.stack[vw.frame].mode = m
171175
switch m {
172176
case mDocument, mArray, mCodeWithScope:
173-
vw.reserveLength() // WARN: this is not needed
177+
vw.reserveLength()
174178
}
175179
}
176180

@@ -209,7 +213,6 @@ func newValueWriter(w io.Writer) *valueWriter {
209213
return vw
210214
}
211215

212-
// TODO: only used in tests
213216
func newValueWriterFromSlice(buf []byte) *valueWriter {
214217
vw := new(valueWriter)
215218
stack := make([]vwState, 1, 5)
@@ -246,16 +249,17 @@ func (vw *valueWriter) invalidTransitionError(destination mode, name string, mod
246249
}
247250

248251
func (vw *valueWriter) writeElementHeader(t bsontype.Type, destination mode, callerName string, addmodes ...mode) error {
249-
frame := &vw.stack[vw.frame]
250-
switch frame.mode {
252+
switch vw.stack[vw.frame].mode {
251253
case mElement:
252-
key := frame.key
254+
key := vw.stack[vw.frame].key
253255
if !isValidCString(key) {
254256
return errors.New("BSON element key cannot contain null bytes")
255257
}
256-
vw.appendHeader(t, key)
258+
259+
vw.buf = bsoncore.AppendHeader(vw.buf, t, key)
257260
case mValue:
258-
vw.appendIntHeader(t, frame.arrkey)
261+
// TODO: Do this with a cache of the first 1000 or so array keys.
262+
vw.buf = bsoncore.AppendHeader(vw.buf, t, strconv.Itoa(vw.stack[vw.frame].arrkey))
259263
default:
260264
modes := []mode{mElement, mValue}
261265
if addmodes != nil {
@@ -597,11 +601,9 @@ func (vw *valueWriter) writeLength() error {
597601
if length > maxSize {
598602
return errMaxDocumentSizeExceeded{size: int64(len(vw.buf))}
599603
}
600-
frame := &vw.stack[vw.frame]
601-
length = length - int(frame.start)
602-
start := frame.start
604+
length = length - int(vw.stack[vw.frame].start)
605+
start := vw.stack[vw.frame].start
603606

604-
_ = vw.buf[start+3] // BCE
605607
vw.buf[start+0] = byte(length)
606608
vw.buf[start+1] = byte(length >> 8)
607609
vw.buf[start+2] = byte(length >> 16)
@@ -610,31 +612,5 @@ func (vw *valueWriter) writeLength() error {
610612
}
611613

612614
func isValidCString(cs string) bool {
613-
// Disallow the zero byte in a cstring because the zero byte is used as the
614-
// terminating character.
615-
//
616-
// It's safe to check bytes instead of runes because all multibyte UTF-8
617-
// code points start with (binary) 11xxxxxx or 10xxxxxx, so 00000000 (i.e.
618-
// 0) will never be part of a multibyte UTF-8 code point. This logic is the
619-
// same as the "r < utf8.RuneSelf" case in strings.IndexRune but can be
620-
// inlined.
621-
//
622-
// https://cs.opensource.google/go/go/+/refs/tags/go1.21.1:src/strings/strings.go;l=127
623-
return strings.IndexByte(cs, 0) == -1
624-
}
625-
626-
// appendHeader is the same as bsoncore.AppendHeader but does not check if the
627-
// key is a valid C string since the caller has already checked for that.
628-
//
629-
// The caller of this function must check if key is a valid C string.
630-
func (vw *valueWriter) appendHeader(t bsontype.Type, key string) {
631-
vw.buf = bsoncore.AppendType(vw.buf, t)
632-
vw.buf = append(vw.buf, key...)
633-
vw.buf = append(vw.buf, 0x00)
634-
}
635-
636-
func (vw *valueWriter) appendIntHeader(t bsontype.Type, key int) {
637-
vw.buf = bsoncore.AppendType(vw.buf, t)
638-
vw.buf = strconv.AppendInt(vw.buf, int64(key), 10)
639-
vw.buf = append(vw.buf, 0x00)
615+
return !strings.ContainsRune(cs, '\x00')
640616
}

bson/marshal.go

Lines changed: 3 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ package bson
99
import (
1010
"bytes"
1111
"encoding/json"
12-
"sync"
1312

1413
"go.mongodb.org/mongo-driver/bson/bsoncodec"
1514
"go.mongodb.org/mongo-driver/bson/bsonrw"
@@ -142,13 +141,6 @@ func MarshalAppendWithRegistry(r *bsoncodec.Registry, dst []byte, val interface{
142141
return MarshalAppendWithContext(bsoncodec.EncodeContext{Registry: r}, dst, val)
143142
}
144143

145-
// Pool of buffers for marshalling BSON.
146-
var bufPool = sync.Pool{
147-
New: func() interface{} {
148-
return new(bytes.Buffer)
149-
},
150-
}
151-
152144
// MarshalAppendWithContext will encode val as a BSON document using Registry r and EncodeContext ec and append the
153145
// bytes to dst. If dst is not large enough to hold the bytes, it will be grown. If val is not a type that can be
154146
// transformed into a document, MarshalValueAppendWithContext should be used instead.
@@ -170,26 +162,8 @@ var bufPool = sync.Pool{
170162
//
171163
// See [Encoder] for more examples.
172164
func MarshalAppendWithContext(ec bsoncodec.EncodeContext, dst []byte, val interface{}) ([]byte, error) {
173-
sw := bufPool.Get().(*bytes.Buffer)
174-
defer func() {
175-
// Proper usage of a sync.Pool requires each entry to have approximately
176-
// the same memory cost. To obtain this property when the stored type
177-
// contains a variably-sized buffer, we add a hard limit on the maximum
178-
// buffer to place back in the pool. We limit the size to 16MiB because
179-
// that's the maximum wire message size supported by any current MongoDB
180-
// server.
181-
//
182-
// Comment based on
183-
// https://cs.opensource.google/go/go/+/refs/tags/go1.19:src/fmt/print.go;l=147
184-
//
185-
// Recycle byte slices that are smaller than 16MiB and at least half
186-
// occupied.
187-
if sw.Cap() < 16*1024*1024 && sw.Cap()/2 < sw.Len() {
188-
bufPool.Put(sw)
189-
}
190-
}()
191-
192-
sw.Reset()
165+
sw := new(bsonrw.SliceWriter)
166+
*sw = dst
193167
vw := bvwPool.Get(sw)
194168
defer bvwPool.Put(vw)
195169

@@ -210,7 +184,7 @@ func MarshalAppendWithContext(ec bsoncodec.EncodeContext, dst []byte, val interf
210184
return nil, err
211185
}
212186

213-
return append(dst, sw.Bytes()...), nil
187+
return *sw, nil
214188
}
215189

216190
// MarshalValue returns the BSON encoding of val.

0 commit comments

Comments
 (0)