Skip to content

Commit b7e17b3

Browse files
committed
wip: reduce nr allocations
1 parent d23b92a commit b7e17b3

File tree

6 files changed

+153
-92
lines changed

6 files changed

+153
-92
lines changed

Diff for: fieldpath/path.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ func (fp Path) Copy() Path {
8080

8181
// MakePath constructs a Path. The parts may be PathElements, ints, strings.
8282
func MakePath(parts ...interface{}) (Path, error) {
83-
var fp Path
83+
fp := make(Path, 0, len(parts))
8484
for _, p := range parts {
8585
switch t := p.(type) {
8686
case PathElement:

Diff for: fieldpath/serialize-pe.go

+19-13
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,10 @@ limitations under the License.
1717
package fieldpath
1818

1919
import (
20+
"bytes"
2021
"errors"
2122
"fmt"
22-
"io"
2323
"strconv"
24-
"strings"
2524

2625
"sigs.k8s.io/structured-merge-diff/v4/value"
2726
)
@@ -57,7 +56,7 @@ var (
5756
func DeserializePathElement(s string) (PathElement, error) {
5857
b := []byte(s)
5958
if len(b) < 2 {
60-
return PathElement{}, errors.New("key must be 2 characters long:")
59+
return PathElement{}, errors.New("key must be 2 characters long")
6160
}
6261
typeSep, b := b[:2], b[2:]
6362
if typeSep[1] != peSepBytes[0] {
@@ -99,41 +98,48 @@ func DeserializePathElement(s string) (PathElement, error) {
9998

10099
// SerializePathElement serializes a path element
101100
func SerializePathElement(pe PathElement) (string, error) {
102-
buf := strings.Builder{}
101+
buf := bytes.Buffer{}
103102
err := serializePathElementToWriter(&buf, pe)
104103
return buf.String(), err
105104
}
106105

107-
func serializePathElementToWriter(w io.Writer, pe PathElement) error {
106+
type noNewlineWriter struct {
107+
*bytes.Buffer
108+
}
109+
110+
func (w noNewlineWriter) Write(p []byte) (n int, err error) {
111+
if len(p) > 0 && p[len(p)-1] == '\n' {
112+
p = p[:len(p)-1]
113+
}
114+
return w.Buffer.Write(p)
115+
}
116+
117+
func serializePathElementToWriter(w *bytes.Buffer, pe PathElement) error {
108118
switch {
109119
case pe.FieldName != nil:
110120
if _, err := w.Write(peFieldSepBytes); err != nil {
111121
return err
112122
}
113-
fmt.Fprintf(w, "%s", *pe.FieldName)
123+
w.WriteString(*pe.FieldName)
114124
case pe.Key != nil:
115125
if _, err := w.Write(peKeySepBytes); err != nil {
116126
return err
117127
}
118-
jsonVal, err := value.FieldListToJSON(*pe.Key)
119-
if err != nil {
128+
if err := value.FieldListToJSON(*pe.Key, w); err != nil {
120129
return err
121130
}
122-
fmt.Fprintf(w, "%s", jsonVal)
123131
case pe.Value != nil:
124132
if _, err := w.Write(peValueSepBytes); err != nil {
125133
return err
126134
}
127-
jsonVal, err := value.ToJSON(*pe.Value)
128-
if err != nil {
135+
if err := value.ToJSON(*pe.Value, w); err != nil {
129136
return err
130137
}
131-
fmt.Fprintf(w, "%s", jsonVal)
132138
case pe.Index != nil:
133139
if _, err := w.Write(peIndexSepBytes); err != nil {
134140
return err
135141
}
136-
fmt.Fprintf(w, "%d", *pe.Index)
142+
w.WriteString(strconv.Itoa(*pe.Index))
137143
default:
138144
return errors.New("invalid PathElement")
139145
}

Diff for: fieldpath/serialize.go

+56-29
Original file line numberDiff line numberDiff line change
@@ -21,30 +21,36 @@ import (
2121
gojson "encoding/json"
2222
"fmt"
2323
"io"
24+
"sort"
25+
"unsafe"
2426

2527
json "sigs.k8s.io/json"
2628
)
2729

2830
func (s *Set) ToJSON() ([]byte, error) {
2931
buf := bytes.Buffer{}
30-
err := s.ToJSONStream(&buf)
32+
err := s.emitContentsV1(false, &buf, &reusableBuilder{})
3133
if err != nil {
3234
return nil, err
3335
}
3436
return buf.Bytes(), nil
3537
}
3638

3739
func (s *Set) ToJSONStream(w io.Writer) error {
38-
err := s.emitContentsV1(false, w)
40+
jsonRaw, err := s.ToJSON()
3941
if err != nil {
4042
return err
4143
}
42-
return nil
44+
45+
_, err = w.Write(jsonRaw)
46+
return err
4347
}
4448

4549
type orderedMapItemWriter struct {
46-
w io.Writer
50+
w *bytes.Buffer
4751
hasItems bool
52+
53+
builder *reusableBuilder
4854
}
4955

5056
// writeKey writes a key to the writer, including a leading comma if necessary.
@@ -69,21 +75,43 @@ func (om *orderedMapItemWriter) writeKey(key []byte) error {
6975
return nil
7076
}
7177

78+
type reusableBuilder struct {
79+
bytes.Buffer
80+
}
81+
82+
func (r *reusableBuilder) reset() *bytes.Buffer {
83+
r.Reset()
84+
return &r.Buffer
85+
}
86+
87+
func (r *reusableBuilder) unsafeString() string {
88+
b := r.Bytes()
89+
return *(*string)(unsafe.Pointer(&b))
90+
}
91+
7292
// writePathKey writes a path element as a key to the writer, including a leading comma if necessary.
7393
// The path will be serialized as a JSON string (including quotes) and passed to writeKey.
7494
// After writing the key, the caller should write the encoded value, e.g. using
7595
// writeEmptyValue or by directly writing the value to the writer.
7696
func (om *orderedMapItemWriter) writePathKey(pe PathElement) error {
77-
pev, err := SerializePathElement(pe)
78-
if err != nil {
97+
if om.hasItems {
98+
if _, err := om.w.Write([]byte{','}); err != nil {
99+
return err
100+
}
101+
}
102+
103+
if err := serializePathElementToWriter(om.builder.reset(), pe); err != nil {
79104
return err
80105
}
81-
key, err := gojson.Marshal(pev)
82-
if err != nil {
106+
if err := gojson.NewEncoder(noNewlineWriter{om.w}).Encode(om.builder.unsafeString()); err != nil {
83107
return err
84108
}
85109

86-
return om.writeKey(key)
110+
if _, err := om.w.Write([]byte{':'}); err != nil {
111+
return err
112+
}
113+
om.hasItems = true
114+
return nil
87115
}
88116

89117
// writeEmptyValue writes an empty JSON object to the writer.
@@ -95,8 +123,8 @@ func (om orderedMapItemWriter) writeEmptyValue() error {
95123
return nil
96124
}
97125

98-
func (s *Set) emitContentsV1(includeSelf bool, w io.Writer) error {
99-
om := orderedMapItemWriter{w: w}
126+
func (s *Set) emitContentsV1(includeSelf bool, w *bytes.Buffer, r *reusableBuilder) error {
127+
om := orderedMapItemWriter{w: w, builder: r}
100128
mi, ci := 0, 0
101129

102130
if _, err := om.w.Write([]byte{'{'}); err != nil {
@@ -129,7 +157,7 @@ func (s *Set) emitContentsV1(includeSelf bool, w io.Writer) error {
129157
if err := om.writePathKey(cpe); err != nil {
130158
return err
131159
}
132-
if err := s.Children.members[ci].set.emitContentsV1(c == 0, om.w); err != nil {
160+
if err := s.Children.members[ci].set.emitContentsV1(c == 0, om.w, r); err != nil {
133161
return err
134162
}
135163

@@ -160,7 +188,7 @@ func (s *Set) emitContentsV1(includeSelf bool, w io.Writer) error {
160188
if err := om.writePathKey(cpe); err != nil {
161189
return err
162190
}
163-
if err := s.Children.members[ci].set.emitContentsV1(false, om.w); err != nil {
191+
if err := s.Children.members[ci].set.emitContentsV1(false, om.w, r); err != nil {
164192
return err
165193
}
166194

@@ -237,34 +265,33 @@ func readIterV1(data []byte) (children *Set, isMember bool, err error) {
237265
children = &Set{}
238266
}
239267

268+
// Append the member to the members list, we will sort it later
240269
m := &children.Members.members
241-
// Since we expect that most of the time these will have been
242-
// serialized in the right order, we just verify that and append.
243-
appendOK := len(*m) == 0 || (*m)[len(*m)-1].Less(pe)
244-
if appendOK {
245-
*m = append(*m, pe)
246-
} else {
247-
children.Members.Insert(pe)
248-
}
270+
*m = append(*m, pe)
249271
}
250272

251273
if v.target != nil {
252274
if children == nil {
253275
children = &Set{}
254276
}
255277

256-
// Since we expect that most of the time these will have been
257-
// serialized in the right order, we just verify that and append.
278+
// Append the child to the children list, we will sort it later
258279
m := &children.Children.members
259-
appendOK := len(*m) == 0 || (*m)[len(*m)-1].pathElement.Less(pe)
260-
if appendOK {
261-
*m = append(*m, setNode{pe, v.target})
262-
} else {
263-
*children.Children.Descend(pe) = *v.target
264-
}
280+
*m = append(*m, setNode{pe, v.target})
265281
}
266282
}
267283

284+
// Sort the members and children
285+
if children != nil {
286+
sort.Slice(children.Members.members, func(i, j int) bool {
287+
return children.Members.members[i].Less(children.Members.members[j])
288+
})
289+
290+
sort.Slice(children.Children.members, func(i, j int) bool {
291+
return children.Children.members[i].pathElement.Less(children.Children.members[j].pathElement)
292+
})
293+
}
294+
268295
if children == nil {
269296
isMember = true
270297
}

Diff for: fieldpath/set_test.go

+46-42
Original file line numberDiff line numberDiff line change
@@ -90,18 +90,20 @@ func BenchmarkFieldSet(b *testing.B) {
9090
}
9191
randOperand := func() *Set { return operands[rand.Intn(len(operands))] }
9292

93-
b.Run(fmt.Sprintf("insert-%v", here.size), func(b *testing.B) {
94-
b.ReportAllocs()
95-
for i := 0; i < b.N; i++ {
96-
makeSet()
97-
}
98-
})
99-
b.Run(fmt.Sprintf("has-%v", here.size), func(b *testing.B) {
100-
b.ReportAllocs()
101-
for i := 0; i < b.N; i++ {
102-
randOperand().Has(randomPathMaker.makePath(here.minPathLen, here.maxPathLen))
103-
}
104-
})
93+
/*
94+
b.Run(fmt.Sprintf("insert-%v", here.size), func(b *testing.B) {
95+
b.ReportAllocs()
96+
for i := 0; i < b.N; i++ {
97+
makeSet()
98+
}
99+
})
100+
b.Run(fmt.Sprintf("has-%v", here.size), func(b *testing.B) {
101+
b.ReportAllocs()
102+
for i := 0; i < b.N; i++ {
103+
randOperand().Has(randomPathMaker.makePath(here.minPathLen, here.maxPathLen))
104+
}
105+
})
106+
*/
105107
b.Run(fmt.Sprintf("serialize-%v", here.size), func(b *testing.B) {
106108
b.ReportAllocs()
107109
for i := 0; i < b.N; i++ {
@@ -116,36 +118,38 @@ func BenchmarkFieldSet(b *testing.B) {
116118
}
117119
})
118120

119-
b.Run(fmt.Sprintf("union-%v", here.size), func(b *testing.B) {
120-
b.ReportAllocs()
121-
for i := 0; i < b.N; i++ {
122-
randOperand().Union(randOperand())
123-
}
124-
})
125-
b.Run(fmt.Sprintf("intersection-%v", here.size), func(b *testing.B) {
126-
b.ReportAllocs()
127-
for i := 0; i < b.N; i++ {
128-
randOperand().Intersection(randOperand())
129-
}
130-
})
131-
b.Run(fmt.Sprintf("difference-%v", here.size), func(b *testing.B) {
132-
b.ReportAllocs()
133-
for i := 0; i < b.N; i++ {
134-
randOperand().Difference(randOperand())
135-
}
136-
})
137-
b.Run(fmt.Sprintf("recursive-difference-%v", here.size), func(b *testing.B) {
138-
b.ReportAllocs()
139-
for i := 0; i < b.N; i++ {
140-
randOperand().RecursiveDifference(randOperand())
141-
}
142-
})
143-
b.Run(fmt.Sprintf("leaves-%v", here.size), func(b *testing.B) {
144-
b.ReportAllocs()
145-
for i := 0; i < b.N; i++ {
146-
randOperand().Leaves()
147-
}
148-
})
121+
/*
122+
b.Run(fmt.Sprintf("union-%v", here.size), func(b *testing.B) {
123+
b.ReportAllocs()
124+
for i := 0; i < b.N; i++ {
125+
randOperand().Union(randOperand())
126+
}
127+
})
128+
b.Run(fmt.Sprintf("intersection-%v", here.size), func(b *testing.B) {
129+
b.ReportAllocs()
130+
for i := 0; i < b.N; i++ {
131+
randOperand().Intersection(randOperand())
132+
}
133+
})
134+
b.Run(fmt.Sprintf("difference-%v", here.size), func(b *testing.B) {
135+
b.ReportAllocs()
136+
for i := 0; i < b.N; i++ {
137+
randOperand().Difference(randOperand())
138+
}
139+
})
140+
b.Run(fmt.Sprintf("recursive-difference-%v", here.size), func(b *testing.B) {
141+
b.ReportAllocs()
142+
for i := 0; i < b.N; i++ {
143+
randOperand().RecursiveDifference(randOperand())
144+
}
145+
})
146+
b.Run(fmt.Sprintf("leaves-%v", here.size), func(b *testing.B) {
147+
b.ReportAllocs()
148+
for i := 0; i < b.N; i++ {
149+
randOperand().Leaves()
150+
}
151+
})
152+
*/
149153
}
150154
}
151155

Diff for: value/fields.go

+17-5
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package value
1818

1919
import (
20+
"bytes"
2021
gojson "encoding/json"
2122
"sort"
2223
"strings"
@@ -50,12 +51,23 @@ func FieldListFromJSON(input []byte) (FieldList, error) {
5051
}
5152

5253
// FieldListToJSON is a helper function for producing a JSON document.
53-
func FieldListToJSON(v FieldList) ([]byte, error) {
54-
m := make(map[string]interface{}, len(v))
55-
for _, f := range v {
56-
m[f.Name] = f.Value.Unstructured()
54+
func FieldListToJSON(v FieldList, w *bytes.Buffer) error {
55+
w.WriteByte('{')
56+
encoder := gojson.NewEncoder(noNewlineWriter{w})
57+
for i, f := range v {
58+
if err := encoder.Encode(f.Name); err != nil {
59+
return err
60+
}
61+
w.WriteByte(':')
62+
if err := encoder.Encode(f.Value.Unstructured()); err != nil {
63+
return err
64+
}
65+
if i < len(v)-1 {
66+
w.WriteByte(',')
67+
}
5768
}
58-
return gojson.Marshal(m)
69+
w.WriteByte('}')
70+
return nil
5971
}
6072

6173
// Sort sorts the field list by Name.

0 commit comments

Comments
 (0)