Skip to content

Commit 4764bac

Browse files
committed
reduce allocations by cheating
1 parent b48d312 commit 4764bac

File tree

2 files changed

+83
-15
lines changed

2 files changed

+83
-15
lines changed

fieldpath/serialize-pe.go

+12-2
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,11 @@ var (
111111
// SerializePathElement serializes a path element
112112
func SerializePathElement(pe PathElement) (string, error) {
113113
buf := strings.Builder{}
114-
stream := writePool.BorrowStream(&buf)
114+
return serializePathElementUsingSB(&buf, pe)
115+
}
116+
117+
func serializePathElementUsingSB(sb *strings.Builder, pe PathElement) (string, error) {
118+
stream := writePool.BorrowStream(sb)
115119
defer writePool.ReturnStream(stream)
116120
switch {
117121
case pe.FieldName != nil:
@@ -138,6 +142,12 @@ func SerializePathElement(pe PathElement) (string, error) {
138142
default:
139143
return "", errors.New("invalid PathElement")
140144
}
145+
b := stream.Buffer()
141146
err := stream.Flush()
142-
return buf.String(), err
147+
// Help jsoniter manage its buffers--without this, the next
148+
// use of the stream is likely to require an allocation. Look
149+
// at the jsoniter stream code to understand why. They were probably
150+
// optimizing for folks using the buffer directly.
151+
stream.SetBuffer(b[:0])
152+
return sb.String(), err
143153
}

fieldpath/serialize.go

+71-13
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,72 @@ package fieldpath
1919
import (
2020
"bytes"
2121
"io"
22+
"strings"
23+
"unsafe"
2224

2325
jsoniter "github.com/json-iterator/go"
2426
)
2527

2628
func (s *Set) ToJSON() ([]byte, error) {
2729
buf := bytes.Buffer{}
28-
stream := jsoniter.NewStream(jsoniter.ConfigCompatibleWithStandardLibrary, &buf, 4096)
30+
err := s.ToJSONStream(&buf)
31+
if err != nil {
32+
return nil, err
33+
}
34+
return buf.Bytes(), nil
35+
}
36+
37+
func (s *Set) ToJSONStream(w io.Writer) error {
38+
stream := writePool.BorrowStream(w)
39+
defer writePool.ReturnStream(stream)
40+
41+
var r reusableBuilder
2942

3043
stream.WriteObjectStart()
31-
s.emitContents_v1(false, stream)
44+
err := s.emitContents_v1(false, stream, &r)
45+
if err != nil {
46+
return err
47+
}
3248
stream.WriteObjectEnd()
33-
err := stream.Flush()
34-
return buf.Bytes(), err
49+
return stream.Flush()
50+
}
51+
52+
func manageMemory(stream *jsoniter.Stream) error {
53+
// Help jsoniter manage its buffers--without this, it does a bunch of
54+
// alloctaions that are not necessary. They were probably optimizing
55+
// for folks using the buffer directly.
56+
b := stream.Buffer()
57+
if len(b) > 4096 || cap(b)-len(b) < 2048 {
58+
if err := stream.Flush(); err != nil {
59+
return err
60+
}
61+
stream.SetBuffer(b[:0])
62+
}
63+
return nil
64+
}
65+
66+
// reusableBuilder is a copy of strings.StringBuilder, which supports copy-free
67+
// []byte -> string transformation. This offers a reset() method which mutates
68+
// internal data, permitting us to reuse the same memory over and over. This
69+
// only makes sense if the string is going to be copied first;, otherwise we'd
70+
// be mutating the string, which is very bad.
71+
//
72+
// We could remove this completely if we modified jsoniter to permit writing a
73+
// string from a []byte.
74+
//
75+
// This MUST match the strings.Builder definition EXACTLY, otherwise reset()
76+
// will do something very bad to the heap.
77+
type reusableBuilder struct {
78+
addr *strings.Builder
79+
buf []byte
80+
}
81+
82+
func (r *reusableBuilder) reset() *strings.Builder {
83+
r.buf = r.buf[:0]
84+
return (*strings.Builder)(unsafe.Pointer(r))
3585
}
3686

37-
func (s *Set) emitContents_v1(includeSelf bool, stream *jsoniter.Stream) {
87+
func (s *Set) emitContents_v1(includeSelf bool, stream *jsoniter.Stream, r *reusableBuilder) error {
3888
mi, ci := 0, 0
3989
first := true
4090
preWrite := func() {
@@ -51,24 +101,28 @@ func (s *Set) emitContents_v1(includeSelf bool, stream *jsoniter.Stream) {
51101

52102
if mpe.Less(cpe) {
53103
preWrite()
54-
str, _ := SerializePathElement(mpe)
104+
str, _ := serializePathElementUsingSB(r.reset(), mpe)
55105
stream.WriteObjectField(str)
56106
stream.WriteEmptyObject()
57107
mi++
58108
} else if cpe.Less(mpe) {
59109
preWrite()
60-
str, _ := SerializePathElement(cpe)
110+
str, _ := serializePathElementUsingSB(r.reset(), cpe)
61111
stream.WriteObjectField(str)
62112
stream.WriteObjectStart()
63-
s.Children.members[ci].set.emitContents_v1(false, stream)
113+
if err := s.Children.members[ci].set.emitContents_v1(false, stream, r); err != nil {
114+
return err
115+
}
64116
stream.WriteObjectEnd()
65117
ci++
66118
} else {
67119
preWrite()
68-
str, _ := SerializePathElement(cpe)
120+
str, _ := serializePathElementUsingSB(r.reset(), cpe)
69121
stream.WriteObjectField(str)
70122
stream.WriteObjectStart()
71-
s.Children.members[ci].set.emitContents_v1(true, stream)
123+
if err := s.Children.members[ci].set.emitContents_v1(true, stream, r); err != nil {
124+
return err
125+
}
72126
stream.WriteObjectEnd()
73127
mi++
74128
ci++
@@ -79,7 +133,7 @@ func (s *Set) emitContents_v1(includeSelf bool, stream *jsoniter.Stream) {
79133
mpe := s.Members.members[mi]
80134

81135
preWrite()
82-
str, _ := SerializePathElement(mpe)
136+
str, _ := serializePathElementUsingSB(r.reset(), mpe)
83137
stream.WriteObjectField(str)
84138
stream.WriteEmptyObject()
85139
mi++
@@ -89,10 +143,12 @@ func (s *Set) emitContents_v1(includeSelf bool, stream *jsoniter.Stream) {
89143
cpe := s.Children.members[ci].pathElement
90144

91145
preWrite()
92-
str, _ := SerializePathElement(cpe)
146+
str, _ := serializePathElementUsingSB(r.reset(), cpe)
93147
stream.WriteObjectField(str)
94148
stream.WriteObjectStart()
95-
s.Children.members[ci].set.emitContents_v1(false, stream)
149+
if err := s.Children.members[ci].set.emitContents_v1(false, stream, r); err != nil {
150+
return err
151+
}
96152
stream.WriteObjectEnd()
97153
ci++
98154
}
@@ -102,10 +158,12 @@ func (s *Set) emitContents_v1(includeSelf bool, stream *jsoniter.Stream) {
102158
stream.WriteObjectField(".")
103159
stream.WriteEmptyObject()
104160
}
161+
return manageMemory(stream)
105162
}
106163

107164
// FromJSON clears s and reads a JSON formatted set structure.
108165
func (s *Set) FromJSON(r io.Reader) error {
166+
// The iterator pool is completely useless for memory management, grrr.
109167
iter := jsoniter.Parse(jsoniter.ConfigCompatibleWithStandardLibrary, r, 4096)
110168

111169
found, _ := readIter_v1(iter)

0 commit comments

Comments
 (0)