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 and addBSON to reuse allocated
[]byte buffers by getting and putting then into a sync.Pool.

Also the function MarshalBuffer was added to the bson package. 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       101203        95119         -6.01%
BenchmarkInsertMultiple-4     1131565       1066556       -5.75%

benchmark                     old allocs     new allocs     delta
BenchmarkInsertSingle-4       50             48             -4.00%
BenchmarkInsertMultiple-4     456            343            -24.78%

benchmark                     old bytes     new bytes     delta
BenchmarkInsertSingle-4       2472          1385          -43.97%
BenchmarkInsertMultiple-4     191180        32525         -82.99%
  • Loading branch information
cezarsa committed Feb 26, 2016
1 parent b20eaf4 commit cdb160e
Show file tree
Hide file tree
Showing 3 changed files with 28 additions and 3 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: 13 additions & 2 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,7 +683,9 @@ 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)
buf := bytesBufferPool.Get().([]byte)
data, err := bson.MarshalBuffer(doc, buf)
defer bytesBufferPool.Put(data[:0])
if err != nil {
return b, err
}
Expand Down

0 comments on commit cdb160e

Please sign in to comment.