-
Notifications
You must be signed in to change notification settings - Fork 892
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
GODRIVER-2914 bsoncodec/bsonrw: eliminate encoding allocations #1323
Changes from 1 commit
f9c485a
4beb9fe
80680c8
603bb4a
6c41835
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -28,6 +28,13 @@ var vwPool = sync.Pool{ | |||||||||||||
}, | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
func putValueWriter(vw *valueWriter) { | ||||||||||||||
if vw != nil { | ||||||||||||||
vw.w = nil // don't leak the writer | ||||||||||||||
vwPool.Put(vw) | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
// BSONValueWriterPool is a pool for BSON ValueWriters. | ||||||||||||||
// | ||||||||||||||
// Deprecated: BSONValueWriterPool will not be supported in Go Driver 2.0. | ||||||||||||||
|
@@ -149,32 +156,21 @@ type valueWriter struct { | |||||||||||||
} | ||||||||||||||
|
||||||||||||||
func (vw *valueWriter) advanceFrame() { | ||||||||||||||
if vw.frame+1 >= int64(len(vw.stack)) { // We need to grow the stack | ||||||||||||||
length := len(vw.stack) | ||||||||||||||
if length+1 >= cap(vw.stack) { | ||||||||||||||
// double it | ||||||||||||||
buf := make([]vwState, 2*cap(vw.stack)+1) | ||||||||||||||
copy(buf, vw.stack) | ||||||||||||||
vw.stack = buf | ||||||||||||||
} | ||||||||||||||
vw.stack = vw.stack[:length+1] | ||||||||||||||
} | ||||||||||||||
vw.frame++ | ||||||||||||||
if vw.frame >= int64(len(vw.stack)) { | ||||||||||||||
vw.stack = append(vw.stack, vwState{}) | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
func (vw *valueWriter) push(m mode) { | ||||||||||||||
vw.advanceFrame() | ||||||||||||||
|
||||||||||||||
// Clean the stack | ||||||||||||||
vw.stack[vw.frame].mode = m | ||||||||||||||
vw.stack[vw.frame].key = "" | ||||||||||||||
vw.stack[vw.frame].arrkey = 0 | ||||||||||||||
vw.stack[vw.frame].start = 0 | ||||||||||||||
vw.stack[vw.frame] = vwState{mode: m} | ||||||||||||||
|
||||||||||||||
vw.stack[vw.frame].mode = m | ||||||||||||||
switch m { | ||||||||||||||
case mDocument, mArray, mCodeWithScope: | ||||||||||||||
vw.reserveLength() | ||||||||||||||
vw.reserveLength() // WARN: this is not needed | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
|
@@ -213,6 +209,7 @@ func newValueWriter(w io.Writer) *valueWriter { | |||||||||||||
return vw | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
// TODO: only used in tests | ||||||||||||||
func newValueWriterFromSlice(buf []byte) *valueWriter { | ||||||||||||||
vw := new(valueWriter) | ||||||||||||||
stack := make([]vwState, 1, 5) | ||||||||||||||
|
@@ -249,17 +246,17 @@ func (vw *valueWriter) invalidTransitionError(destination mode, name string, mod | |||||||||||||
} | ||||||||||||||
|
||||||||||||||
func (vw *valueWriter) writeElementHeader(t bsontype.Type, destination mode, callerName string, addmodes ...mode) error { | ||||||||||||||
switch vw.stack[vw.frame].mode { | ||||||||||||||
frame := &vw.stack[vw.frame] | ||||||||||||||
switch frame.mode { | ||||||||||||||
case mElement: | ||||||||||||||
key := vw.stack[vw.frame].key | ||||||||||||||
key := frame.key | ||||||||||||||
if !isValidCString(key) { | ||||||||||||||
return errors.New("BSON element key cannot contain null bytes") | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
vw.buf = bsoncore.AppendHeader(vw.buf, t, key) | ||||||||||||||
vw.appendHeader(t, key) | ||||||||||||||
case mValue: | ||||||||||||||
// TODO: Do this with a cache of the first 1000 or so array keys. | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Optional: This TODO is unnecessary because
Suggested change
|
||||||||||||||
vw.buf = bsoncore.AppendHeader(vw.buf, t, strconv.Itoa(vw.stack[vw.frame].arrkey)) | ||||||||||||||
vw.appendIntHeader(t, frame.arrkey) | ||||||||||||||
default: | ||||||||||||||
modes := []mode{mElement, mValue} | ||||||||||||||
if addmodes != nil { | ||||||||||||||
|
@@ -601,9 +598,11 @@ func (vw *valueWriter) writeLength() error { | |||||||||||||
if length > maxSize { | ||||||||||||||
return errMaxDocumentSizeExceeded{size: int64(len(vw.buf))} | ||||||||||||||
} | ||||||||||||||
length = length - int(vw.stack[vw.frame].start) | ||||||||||||||
start := vw.stack[vw.frame].start | ||||||||||||||
frame := &vw.stack[vw.frame] | ||||||||||||||
length = length - int(frame.start) | ||||||||||||||
start := frame.start | ||||||||||||||
|
||||||||||||||
_ = vw.buf[start+3] // BCE | ||||||||||||||
vw.buf[start+0] = byte(length) | ||||||||||||||
vw.buf[start+1] = byte(length >> 8) | ||||||||||||||
vw.buf[start+2] = byte(length >> 16) | ||||||||||||||
|
@@ -612,5 +611,21 @@ func (vw *valueWriter) writeLength() error { | |||||||||||||
} | ||||||||||||||
|
||||||||||||||
func isValidCString(cs string) bool { | ||||||||||||||
return !strings.ContainsRune(cs, '\x00') | ||||||||||||||
return strings.IndexByte(cs, 0) == -1 | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Optional: Consider adding a comment about why we can check individual bytes rather than runes.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Real reason is that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we're both saying the same thing. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, I thought you meant |
||||||||||||||
} | ||||||||||||||
|
||||||||||||||
// appendHeader is the same as bsoncore.AppendHeader but does not check if the | ||||||||||||||
// key is a valid C string since the caller has already checked for that. | ||||||||||||||
// | ||||||||||||||
// The caller of this function must check if key is a valid C string. | ||||||||||||||
func (vw *valueWriter) appendHeader(t bsontype.Type, key string) { | ||||||||||||||
vw.buf = bsoncore.AppendType(vw.buf, t) | ||||||||||||||
vw.buf = append(vw.buf, key...) | ||||||||||||||
vw.buf = append(vw.buf, 0x00) | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
func (vw *valueWriter) appendIntHeader(t bsontype.Type, key int) { | ||||||||||||||
vw.buf = bsoncore.AppendType(vw.buf, t) | ||||||||||||||
vw.buf = strconv.AppendInt(vw.buf, int64(key), 10) | ||||||||||||||
vw.buf = append(vw.buf, 0x00) | ||||||||||||||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -9,6 +9,7 @@ package bson | |||||||||||||||||||||||||||
import ( | ||||||||||||||||||||||||||||
"bytes" | ||||||||||||||||||||||||||||
"encoding/json" | ||||||||||||||||||||||||||||
"sync" | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
"go.mongodb.org/mongo-driver/bson/bsoncodec" | ||||||||||||||||||||||||||||
"go.mongodb.org/mongo-driver/bson/bsonrw" | ||||||||||||||||||||||||||||
|
@@ -141,6 +142,13 @@ func MarshalAppendWithRegistry(r *bsoncodec.Registry, dst []byte, val interface{ | |||||||||||||||||||||||||||
return MarshalAppendWithContext(bsoncodec.EncodeContext{Registry: r}, dst, val) | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
// Pool of buffers for marshalling BSON. | ||||||||||||||||||||||||||||
var bufPool = sync.Pool{ | ||||||||||||||||||||||||||||
New: func() interface{} { | ||||||||||||||||||||||||||||
return new(bytes.Buffer) | ||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
// MarshalAppendWithContext will encode val as a BSON document using Registry r and EncodeContext ec and append the | ||||||||||||||||||||||||||||
// 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 | ||||||||||||||||||||||||||||
// transformed into a document, MarshalValueAppendWithContext should be used instead. | ||||||||||||||||||||||||||||
|
@@ -162,8 +170,9 @@ func MarshalAppendWithRegistry(r *bsoncodec.Registry, dst []byte, val interface{ | |||||||||||||||||||||||||||
// | ||||||||||||||||||||||||||||
// See [Encoder] for more examples. | ||||||||||||||||||||||||||||
func MarshalAppendWithContext(ec bsoncodec.EncodeContext, dst []byte, val interface{}) ([]byte, error) { | ||||||||||||||||||||||||||||
sw := new(bsonrw.SliceWriter) | ||||||||||||||||||||||||||||
*sw = dst | ||||||||||||||||||||||||||||
sw := bufPool.Get().(*bytes.Buffer) | ||||||||||||||||||||||||||||
defer bufPool.Put(sw) | ||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should check the size and utilization of of E.g. defer func() {
if sw.Cap() < 16*1024*1024 && sw.Cap()/2 < sw.Len() {
bufPool.Put(sw)
}
}() See the similar code in mongo-go-driver/x/mongo/driver/operation.go Lines 519 to 531 in 99d53d6
|
||||||||||||||||||||||||||||
sw.Reset() | ||||||||||||||||||||||||||||
vw := bvwPool.Get(sw) | ||||||||||||||||||||||||||||
defer bvwPool.Put(vw) | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
|
@@ -184,7 +193,7 @@ func MarshalAppendWithContext(ec bsoncodec.EncodeContext, dst []byte, val interf | |||||||||||||||||||||||||||
return nil, err | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
return *sw, nil | ||||||||||||||||||||||||||||
return append(dst, sw.Bytes()...), nil | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
// MarshalValue returns the BSON encoding of val. | ||||||||||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Optional: Consider the slightly more descriptive name
skipCString
.