From 71522ded00fb69f528111bc98669399feb3d9acb Mon Sep 17 00:00:00 2001 From: Cezar Sa Espinola Date: Fri, 26 Feb 2016 18:50:57 -0300 Subject: [PATCH 1/2] Add benchmarks for inserting documents --- session_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/session_test.go b/session_test.go index 21259326e..92b6b52b2 100644 --- a/session_test.go +++ b/session_test.go @@ -34,6 +34,7 @@ import ( "sort" "strconv" "strings" + "testing" "time" . "gopkg.in/check.v1" @@ -4092,3 +4093,46 @@ func (s *S) BenchmarkFindIterRaw(c *C) { c.Assert(iter.Err(), IsNil) c.Assert(i, Equals, c.N) } + +func BenchmarkInsertSingle(b *testing.B) { + session, err := mgo.Dial("localhost:40001") + if err != nil { + b.Fatal(err) + } + defer session.Close() + + doc := bson.D{ + {"A", strings.Repeat("*", 256)}, + } + coll := session.DB("mydb").C("benchmarkcoll") + b.ResetTimer() + for i := 0; i < b.N; i++ { + err := coll.Insert(doc) + if err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkInsertMultiple(b *testing.B) { + session, err := mgo.Dial("localhost:40001") + if err != nil { + b.Fatal(err) + } + defer session.Close() + + docs := make([]interface{}, 100) + for i := range docs { + docs[i] = bson.D{ + {"A", strings.Repeat("*", 256)}, + } + } + coll := session.DB("mydb").C("benchmarkcoll") + b.ResetTimer() + for i := 0; i < b.N; i++ { + err := coll.Insert(docs...) + if err != nil { + b.Fatal(err) + } + } +} From 9810e051e60e10f5bd26c53d7bcae7f341e2b017 Mon Sep 17 00:00:00 2001 From: Cezar Sa Espinola Date: Fri, 26 Feb 2016 19:03:43 -0300 Subject: [PATCH 2/2] Reduce memory footprint by reusing buffers 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% --- bson/bson.go | 9 ++++++++- bson/bson_test.go | 7 +++++++ socket.go | 15 ++++++++++++--- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/bson/bson.go b/bson/bson.go index ac1c02c7f..6bfad20b0 100644 --- a/bson/bson.go +++ b/bson/bson.go @@ -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 } diff --git a/bson/bson_test.go b/bson/bson_test.go index b77ec877f..5866539b6 100644 --- a/bson/bson_test.go +++ b/bson/bson_test.go @@ -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. diff --git a/socket.go b/socket.go index a2343354d..3d498b7d4 100644 --- a/socket.go +++ b/socket.go @@ -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. @@ -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) {