Skip to content

Commit

Permalink
Reduce memory footprint by reusing buffers
Browse files Browse the repository at this point in the history
This commit changes mongoSocket.Query to reuse allocated []byte buffers
by getting and putting then into a sync.Pool.

Also the function MarshalBuffer was added to the bson package and the
addBSON function now uses it. It behaves in the exact same way to
Marshal but instead of allocating a []byte buffer it receives an already
existing buffer.

Benchmarks show a really nice decrease in memory footprint specially
when large messages are being sent to MongoDB, as is the case of
inserting multiple documents at once.

$ benchcmp before.txt after.txt
benchmark                     old ns/op     new ns/op     delta
BenchmarkInsertSingle-4       94191         90779         -3.62%
BenchmarkInsertMultiple-4     1076423       937569        -12.90%

benchmark                     old allocs     new allocs     delta
BenchmarkInsertSingle-4       50             46             -8.00%
BenchmarkInsertMultiple-4     456            242            -46.93%

benchmark                     old bytes     new bytes     delta
BenchmarkInsertSingle-4       2472          1320          -46.60%
BenchmarkInsertMultiple-4     191180        29228         -84.71%
  • Loading branch information
cezarsa committed Feb 27, 2016
1 parent 699256f commit d9af486
Show file tree
Hide file tree
Showing 3 changed files with 27 additions and 4 deletions.
9 changes: 8 additions & 1 deletion bson/bson.go
Original file line number Diff line number Diff line change
Expand Up @@ -489,8 +489,15 @@ func handleErr(err *error) {
// }
//
func Marshal(in interface{}) (out []byte, err error) {
return MarshalBuffer(in, make([]byte, 0, initialBufferSize))
}

// MarshalBuffer behaves the same way as Marshal, except that instead of
// allocating a new byte slice it tries to use the received byte slice and
// only allocates more memory if necessary to fit the marshaled value.
func MarshalBuffer(in interface{}, buf []byte) (out []byte, err error) {
defer handleErr(&err)
e := &encoder{make([]byte, 0, initialBufferSize)}
e := &encoder{buf}
e.addDoc(reflect.ValueOf(in))
return e.out, nil
}
Expand Down
7 changes: 7 additions & 0 deletions bson/bson_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,13 @@ func (s *S) TestUnmarshalNonNilInterface(c *C) {
c.Assert(m, DeepEquals, bson.M{"a": 1})
}

func (s *S) TestMarshalBuffer(c *C) {
buf := make([]byte, 0, 256)
data, err := bson.MarshalBuffer(bson.M{"a": 1}, buf)
c.Assert(err, IsNil)
c.Assert(data, DeepEquals, buf[:len(data)])
}

// --------------------------------------------------------------------------
// Some one way marshaling operations which would unmarshal differently.

Expand Down
15 changes: 12 additions & 3 deletions socket.go
Original file line number Diff line number Diff line change
Expand Up @@ -372,13 +372,22 @@ func (socket *mongoSocket) SimpleQuery(op *queryOp) (data []byte, err error) {
return data, err
}

var bytesBufferPool = sync.Pool{
New: func() interface{} {
return make([]byte, 0, 256)
},
}

func (socket *mongoSocket) Query(ops ...interface{}) (err error) {

if lops := socket.flushLogout(); len(lops) > 0 {
ops = append(lops, ops...)
}

buf := make([]byte, 0, 256)
buf := bytesBufferPool.Get().([]byte)
defer func() {
bytesBufferPool.Put(buf[:0])
}()

// Serialize operations synchronously to avoid interrupting
// other goroutines while we can't really be sending data.
Expand Down Expand Up @@ -674,11 +683,11 @@ func addBSON(b []byte, doc interface{}) ([]byte, error) {
if doc == nil {
return append(b, 5, 0, 0, 0, 0), nil
}
data, err := bson.Marshal(doc)
data, err := bson.MarshalBuffer(doc, b)
if err != nil {
return b, err
}
return append(b, data...), nil
return data, nil
}

func setInt32(b []byte, pos int, i int32) {
Expand Down

0 comments on commit d9af486

Please sign in to comment.