Skip to content

Commit de9108a

Browse files
authored
GODRIVER-2734 Benchmark and reduce allocations in bson.Raw.String (#1160)
1 parent 60cfd91 commit de9108a

File tree

4 files changed

+89
-11
lines changed

4 files changed

+89
-11
lines changed

bson/raw_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"encoding/binary"
1212
"fmt"
1313
"io"
14+
"strings"
1415
"testing"
1516

1617
"github.com/google/go-cmp/cmp"
@@ -345,3 +346,80 @@ func TestRaw(t *testing.T) {
345346
}
346347
})
347348
}
349+
350+
func BenchmarkRawString(b *testing.B) {
351+
// Create 1KiB and 128B strings to exercise the string-heavy call paths in
352+
// the "Raw.String" method.
353+
var buf strings.Builder
354+
for i := 0; i < 128; i++ {
355+
buf.WriteString("abcdefgh")
356+
}
357+
str1k := buf.String()
358+
str128 := str1k[:128]
359+
360+
cases := []struct {
361+
description string
362+
value interface{}
363+
}{
364+
{
365+
description: "string",
366+
value: D{{Key: "key", Value: str128}},
367+
},
368+
{
369+
description: "integer",
370+
value: D{{Key: "key", Value: int64(1234567890)}},
371+
},
372+
{
373+
description: "float",
374+
value: D{{Key: "key", Value: float64(1234567890.123456789)}},
375+
},
376+
{
377+
description: "nested document",
378+
value: D{{
379+
Key: "key",
380+
Value: D{{
381+
Key: "key",
382+
Value: D{{
383+
Key: "key",
384+
Value: str128,
385+
}},
386+
}},
387+
}},
388+
},
389+
{
390+
description: "array of strings",
391+
value: D{{
392+
Key: "key",
393+
Value: []string{str128, str128, str128, str128},
394+
}},
395+
},
396+
{
397+
description: "mixed struct",
398+
value: struct {
399+
Key1 struct {
400+
Nested string
401+
}
402+
Key2 string
403+
Key3 int64
404+
Key4 float64
405+
}{
406+
Key1: struct{ Nested string }{Nested: str1k},
407+
Key2: str1k,
408+
Key3: 1234567890,
409+
Key4: 1234567890.123456789,
410+
},
411+
},
412+
}
413+
414+
for _, tc := range cases {
415+
b.Run(tc.description, func(b *testing.B) {
416+
bs, err := Marshal(tc.value)
417+
require.NoError(b, err)
418+
419+
b.ResetTimer()
420+
for i := 0; i < b.N; i++ {
421+
_ = Raw(bs).String()
422+
}
423+
})
424+
}
425+
}

x/bsonx/bsoncore/array.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
package bsoncore
88

99
import (
10-
"bytes"
1110
"fmt"
1211
"io"
1312
"strconv"
13+
"strings"
1414
)
1515

1616
// NewArrayLengthError creates and returns an error for when the length of an array exceeds the
@@ -53,7 +53,7 @@ func (a Array) DebugString() string {
5353
if len(a) < 5 {
5454
return "<malformed>"
5555
}
56-
var buf bytes.Buffer
56+
var buf strings.Builder
5757
buf.WriteString("Array")
5858
length, rem, _ := ReadLength(a) // We know we have enough bytes to read the length
5959
buf.WriteByte('(')
@@ -69,7 +69,7 @@ func (a Array) DebugString() string {
6969
buf.WriteString(fmt.Sprintf("<malformed (%d)>", length))
7070
break
7171
}
72-
fmt.Fprintf(&buf, "%s", elem.Value().DebugString())
72+
buf.WriteString(elem.Value().DebugString())
7373
if length != 1 {
7474
buf.WriteByte(',')
7575
}
@@ -85,7 +85,7 @@ func (a Array) String() string {
8585
if len(a) < 5 {
8686
return ""
8787
}
88-
var buf bytes.Buffer
88+
var buf strings.Builder
8989
buf.WriteByte('[')
9090

9191
length, rem, _ := ReadLength(a) // We know we have enough bytes to read the length
@@ -100,7 +100,7 @@ func (a Array) String() string {
100100
if !ok {
101101
return ""
102102
}
103-
fmt.Fprintf(&buf, "%s", elem.Value().String())
103+
buf.WriteString(elem.Value().String())
104104
if length > 1 {
105105
buf.WriteByte(',')
106106
}

x/bsonx/bsoncore/document.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
package bsoncore
88

99
import (
10-
"bytes"
1110
"errors"
1211
"fmt"
1312
"io"
1413
"strconv"
14+
"strings"
1515

1616
"go.mongodb.org/mongo-driver/bson/bsontype"
1717
)
@@ -237,7 +237,7 @@ func (d Document) DebugString() string {
237237
if len(d) < 5 {
238238
return "<malformed>"
239239
}
240-
var buf bytes.Buffer
240+
var buf strings.Builder
241241
buf.WriteString("Document")
242242
length, rem, _ := ReadLength(d) // We know we have enough bytes to read the length
243243
buf.WriteByte('(')
@@ -253,7 +253,7 @@ func (d Document) DebugString() string {
253253
buf.WriteString(fmt.Sprintf("<malformed (%d)>", length))
254254
break
255255
}
256-
fmt.Fprintf(&buf, "%s ", elem.DebugString())
256+
buf.WriteString(elem.DebugString())
257257
}
258258
buf.WriteByte('}')
259259

@@ -266,7 +266,7 @@ func (d Document) String() string {
266266
if len(d) < 5 {
267267
return ""
268268
}
269-
var buf bytes.Buffer
269+
var buf strings.Builder
270270
buf.WriteByte('{')
271271

272272
length, rem, _ := ReadLength(d) // We know we have enough bytes to read the length
@@ -285,7 +285,7 @@ func (d Document) String() string {
285285
if !ok {
286286
return ""
287287
}
288-
fmt.Fprintf(&buf, "%s", elem.String())
288+
buf.WriteString(elem.String())
289289
first = false
290290
}
291291
buf.WriteByte('}')

x/bsonx/bsoncore/element.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ func (e Element) String() string {
129129
if !valid {
130130
return ""
131131
}
132-
return fmt.Sprintf(`"%s": %v`, key, val)
132+
return "\"" + string(key) + "\": " + val.String()
133133
}
134134

135135
// DebugString outputs a human readable version of RawElement. It will attempt to stringify the

0 commit comments

Comments
 (0)