Skip to content

Commit 1791273

Browse files
authored
Merge pull request #155 from jpbetz/freelist-optimization
Use freelists instead of sync.Pool for value object recycling
2 parents b8515d0 + 0e517d5 commit 1791273

20 files changed

+559
-259
lines changed

fieldpath/fromvalue.go

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,20 @@ func SetFromValue(v value.Value) *Set {
2525
s := NewSet()
2626

2727
w := objectWalker{
28-
path: Path{},
29-
value: v,
30-
do: func(p Path) { s.Insert(p) },
28+
path: Path{},
29+
value: v,
30+
allocator: value.NewFreelistAllocator(),
31+
do: func(p Path) { s.Insert(p) },
3132
}
3233

3334
w.walk()
3435
return s
3536
}
3637

3738
type objectWalker struct {
38-
path Path
39-
value value.Value
39+
path Path
40+
value value.Value
41+
allocator value.Allocator
4042

4143
do func(Path)
4244
}
@@ -55,14 +57,14 @@ func (w *objectWalker) walk() {
5557
case w.value.IsList():
5658
// If the list were atomic, we'd break here, but we don't have
5759
// a schema, so we can't tell.
58-
l := w.value.AsList()
59-
defer l.Recycle()
60-
iter := l.Range()
61-
defer iter.Recycle()
60+
l := w.value.AsListUsing(w.allocator)
61+
defer w.allocator.Free(l)
62+
iter := l.RangeUsing(w.allocator)
63+
defer w.allocator.Free(iter)
6264
for iter.Next() {
6365
i, value := iter.Item()
6466
w2 := *w
65-
w2.path = append(w.path, GuessBestListPathElement(i, value))
67+
w2.path = append(w.path, w.GuessBestListPathElement(i, value))
6668
w2.value = value
6769
w2.walk()
6870
}
@@ -71,9 +73,9 @@ func (w *objectWalker) walk() {
7173
// If the map/struct were atomic, we'd break here, but we don't
7274
// have a schema, so we can't tell.
7375

74-
m := w.value.AsMap()
75-
defer m.Recycle()
76-
m.Iterate(func(k string, val value.Value) bool {
76+
m := w.value.AsMapUsing(w.allocator)
77+
defer w.allocator.Free(m)
78+
m.IterateUsing(w.allocator, func(k string, val value.Value) bool {
7779
w2 := *w
7880
w2.path = append(w.path, PathElement{FieldName: &k})
7981
w2.value = val
@@ -102,16 +104,16 @@ var AssociativeListCandidateFieldNames = []string{
102104
// referencing by index is acceptable. Currently this is done by checking
103105
// whether item has any of the fields listed in
104106
// AssociativeListCandidateFieldNames which have scalar values.
105-
func GuessBestListPathElement(index int, item value.Value) PathElement {
107+
func (w *objectWalker) GuessBestListPathElement(index int, item value.Value) PathElement {
106108
if !item.IsMap() {
107109
// Non map items could be parts of sets or regular "atomic"
108110
// lists. We won't try to guess whether something should be a
109111
// set or not.
110112
return PathElement{Index: &index}
111113
}
112114

113-
m := item.AsMap()
114-
defer m.Recycle()
115+
m := item.AsMapUsing(w.allocator)
116+
defer w.allocator.Free(m)
115117
var keys value.FieldList
116118
for _, name := range AssociativeListCandidateFieldNames {
117119
f, ok := m.Get(name)

typed/helpers.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -154,19 +154,19 @@ func handleAtom(a schema.Atom, tr schema.TypeRef, ah atomHandler) ValidationErro
154154
}
155155

156156
// Returns the list, or an error. Reminder: nil is a valid list and might be returned.
157-
func listValue(val value.Value) (value.List, error) {
157+
func listValue(a value.Allocator, val value.Value) (value.List, error) {
158158
if val.IsNull() {
159159
// Null is a valid list.
160160
return nil, nil
161161
}
162162
if !val.IsList() {
163163
return nil, fmt.Errorf("expected list, got %v", val)
164164
}
165-
return val.AsList(), nil
165+
return val.AsListUsing(a), nil
166166
}
167167

168168
// Returns the map, or an error. Reminder: nil is a valid map and might be returned.
169-
func mapValue(val value.Value) (value.Map, error) {
169+
func mapValue(a value.Allocator, val value.Value) (value.Map, error) {
170170
if val == nil {
171171
return nil, fmt.Errorf("expected map, got nil")
172172
}
@@ -177,10 +177,10 @@ func mapValue(val value.Value) (value.Map, error) {
177177
if !val.IsMap() {
178178
return nil, fmt.Errorf("expected map, got %v", val)
179179
}
180-
return val.AsMap(), nil
180+
return val.AsMapUsing(a), nil
181181
}
182182

183-
func keyedAssociativeListItemToPathElement(list *schema.List, index int, child value.Value) (fieldpath.PathElement, error) {
183+
func keyedAssociativeListItemToPathElement(a value.Allocator, list *schema.List, index int, child value.Value) (fieldpath.PathElement, error) {
184184
pe := fieldpath.PathElement{}
185185
if child.IsNull() {
186186
// For now, the keys are required which means that null entries
@@ -191,8 +191,8 @@ func keyedAssociativeListItemToPathElement(list *schema.List, index int, child v
191191
return pe, errors.New("associative list with keys may not have non-map elements")
192192
}
193193
keyMap := value.FieldList{}
194-
m := child.AsMap()
195-
defer m.Recycle()
194+
m := child.AsMapUsing(a)
195+
defer a.Free(m)
196196
for _, fieldName := range list.Keys {
197197
if val, ok := m.Get(fieldName); ok {
198198
keyMap = append(keyMap, value.Field{Name: fieldName, Value: val})
@@ -225,10 +225,10 @@ func setItemToPathElement(list *schema.List, index int, child value.Value) (fiel
225225
}
226226
}
227227

228-
func listItemToPathElement(list *schema.List, index int, child value.Value) (fieldpath.PathElement, error) {
228+
func listItemToPathElement(a value.Allocator, list *schema.List, index int, child value.Value) (fieldpath.PathElement, error) {
229229
if list.ElementRelationship == schema.Associative {
230230
if len(list.Keys) > 0 {
231-
return keyedAssociativeListItemToPathElement(list, index, child)
231+
return keyedAssociativeListItemToPathElement(a, list, index, child)
232232
}
233233

234234
// If there's no keys, then we must be a set of primitives.

typed/merge.go

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ type mergingWalker struct {
4848

4949
// Allocate only as many walkers as needed for the depth by storing them here.
5050
spareWalkers *[]*mergingWalker
51+
52+
allocator value.Allocator
5153
}
5254

5355
// merge rules examine w.lhs and w.rhs (up to one of which may be nil) and
@@ -152,7 +154,7 @@ func (w *mergingWalker) derefMap(prefix string, v value.Value) (value.Map, Valid
152154
if v == nil {
153155
return nil, nil
154156
}
155-
m, err := mapValue(v)
157+
m, err := mapValue(w.allocator, v)
156158
if err != nil {
157159
return nil, errorf("%v: %v", prefix, err)
158160
}
@@ -181,7 +183,7 @@ func (w *mergingWalker) visitListItems(t *schema.List, lhs, rhs value.List) (err
181183
if rhs != nil {
182184
for i := 0; i < rhs.Length(); i++ {
183185
child := rhs.At(i)
184-
pe, err := listItemToPathElement(t, i, child)
186+
pe, err := listItemToPathElement(w.allocator, t, i, child)
185187
if err != nil {
186188
errs = append(errs, errorf("rhs: element %v: %v", i, err.Error())...)
187189
// If we can't construct the path element, we can't
@@ -202,7 +204,7 @@ func (w *mergingWalker) visitListItems(t *schema.List, lhs, rhs value.List) (err
202204
if lhs != nil {
203205
for i := 0; i < lhs.Length(); i++ {
204206
child := lhs.At(i)
205-
pe, err := listItemToPathElement(t, i, child)
207+
pe, err := listItemToPathElement(w.allocator, t, i, child)
206208
if err != nil {
207209
errs = append(errs, errorf("lhs: element %v: %v", i, err.Error())...)
208210
// If we can't construct the path element, we can't
@@ -254,7 +256,7 @@ func (w *mergingWalker) derefList(prefix string, v value.Value) (value.List, Val
254256
if v == nil {
255257
return nil, nil
256258
}
257-
l, err := listValue(v)
259+
l, err := listValue(w.allocator, v)
258260
if err != nil {
259261
return nil, errorf("%v: %v", prefix, err)
260262
}
@@ -264,11 +266,11 @@ func (w *mergingWalker) derefList(prefix string, v value.Value) (value.List, Val
264266
func (w *mergingWalker) doList(t *schema.List) (errs ValidationErrors) {
265267
lhs, _ := w.derefList("lhs: ", w.lhs)
266268
if lhs != nil {
267-
defer lhs.Recycle()
269+
defer w.allocator.Free(lhs)
268270
}
269271
rhs, _ := w.derefList("rhs: ", w.rhs)
270272
if rhs != nil {
271-
defer rhs.Recycle()
273+
defer w.allocator.Free(rhs)
272274
}
273275

274276
// If both lhs and rhs are empty/null, treat it as a
@@ -310,7 +312,7 @@ func (w *mergingWalker) visitMapItem(t *schema.Map, out map[string]interface{},
310312
func (w *mergingWalker) visitMapItems(t *schema.Map, lhs, rhs value.Map) (errs ValidationErrors) {
311313
out := map[string]interface{}{}
312314

313-
value.MapZip(lhs, rhs, value.Unordered, func(key string, lhsValue, rhsValue value.Value) bool {
315+
value.MapZipUsing(w.allocator, lhs, rhs, value.Unordered, func(key string, lhsValue, rhsValue value.Value) bool {
314316
errs = append(errs, w.visitMapItem(t, out, key, lhsValue, rhsValue)...)
315317
return true
316318
})
@@ -325,11 +327,11 @@ func (w *mergingWalker) visitMapItems(t *schema.Map, lhs, rhs value.Map) (errs V
325327
func (w *mergingWalker) doMap(t *schema.Map) (errs ValidationErrors) {
326328
lhs, _ := w.derefMap("lhs: ", w.lhs)
327329
if lhs != nil {
328-
defer lhs.Recycle()
330+
defer w.allocator.Free(lhs)
329331
}
330332
rhs, _ := w.derefMap("rhs: ", w.rhs)
331333
if rhs != nil {
332-
defer rhs.Recycle()
334+
defer w.allocator.Free(rhs)
333335
}
334336
// If both lhs and rhs are empty/null, treat it as a
335337
// leaf: this helps preserve the empty/null

typed/remove.go

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,19 @@ import (
2020
)
2121

2222
type removingWalker struct {
23-
value value.Value
24-
out interface{}
25-
schema *schema.Schema
26-
toRemove *fieldpath.Set
23+
value value.Value
24+
out interface{}
25+
schema *schema.Schema
26+
toRemove *fieldpath.Set
27+
allocator value.Allocator
2728
}
2829

2930
func removeItemsWithSchema(val value.Value, toRemove *fieldpath.Set, schema *schema.Schema, typeRef schema.TypeRef) value.Value {
3031
w := &removingWalker{
31-
value: val,
32-
schema: schema,
33-
toRemove: toRemove,
32+
value: val,
33+
schema: schema,
34+
toRemove: toRemove,
35+
allocator: value.NewFreelistAllocator(),
3436
}
3537
resolveSchema(schema, typeRef, val, w)
3638
return value.NewValueInterface(w.out)
@@ -42,20 +44,20 @@ func (w *removingWalker) doScalar(t *schema.Scalar) ValidationErrors {
4244
}
4345

4446
func (w *removingWalker) doList(t *schema.List) (errs ValidationErrors) {
45-
l := w.value.AsList()
46-
defer l.Recycle()
47+
l := w.value.AsListUsing(w.allocator)
48+
defer w.allocator.Free(l)
4749
// If list is null, empty, or atomic just return
4850
if l == nil || l.Length() == 0 || t.ElementRelationship == schema.Atomic {
4951
return nil
5052
}
5153

5254
var newItems []interface{}
53-
iter := l.Range()
54-
defer iter.Recycle()
55+
iter := l.RangeUsing(w.allocator)
56+
defer w.allocator.Free(iter)
5557
for iter.Next() {
5658
i, item := iter.Item()
5759
// Ignore error because we have already validated this list
58-
pe, _ := listItemToPathElement(t, i, item)
60+
pe, _ := listItemToPathElement(w.allocator, t, i, item)
5961
path, _ := fieldpath.MakePath(pe)
6062
if w.toRemove.Has(path) {
6163
continue
@@ -72,9 +74,9 @@ func (w *removingWalker) doList(t *schema.List) (errs ValidationErrors) {
7274
}
7375

7476
func (w *removingWalker) doMap(t *schema.Map) ValidationErrors {
75-
m := w.value.AsMap()
77+
m := w.value.AsMapUsing(w.allocator)
7678
if m != nil {
77-
defer m.Recycle()
79+
defer w.allocator.Free(m)
7880
}
7981
// If map is null, empty, or atomic just return
8082
if m == nil || m.Empty() || t.ElementRelationship == schema.Atomic {

typed/tofieldset.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ func (tv TypedValue) toFieldSetWalker() *toFieldSetWalker {
3434
v.schema = tv.schema
3535
v.typeRef = tv.typeRef
3636
v.set = &fieldpath.Set{}
37+
v.allocator = value.NewFreelistAllocator()
3738
return v
3839
}
3940

@@ -55,6 +56,7 @@ type toFieldSetWalker struct {
5556

5657
// Allocate only as many walkers as needed for the depth by storing them here.
5758
spareWalkers *[]*toFieldSetWalker
59+
allocator value.Allocator
5860
}
5961

6062
func (v *toFieldSetWalker) prepareDescent(pe fieldpath.PathElement, tr schema.TypeRef) *toFieldSetWalker {
@@ -94,7 +96,7 @@ func (v *toFieldSetWalker) doScalar(t *schema.Scalar) ValidationErrors {
9496
func (v *toFieldSetWalker) visitListItems(t *schema.List, list value.List) (errs ValidationErrors) {
9597
for i := 0; i < list.Length(); i++ {
9698
child := list.At(i)
97-
pe, _ := listItemToPathElement(t, i, child)
99+
pe, _ := listItemToPathElement(v.allocator, t, i, child)
98100
v2 := v.prepareDescent(pe, t.ElementType)
99101
v2.value = child
100102
errs = append(errs, v2.toFieldSet()...)
@@ -106,8 +108,10 @@ func (v *toFieldSetWalker) visitListItems(t *schema.List, list value.List) (errs
106108
}
107109

108110
func (v *toFieldSetWalker) doList(t *schema.List) (errs ValidationErrors) {
109-
list, _ := listValue(v.value)
110-
111+
list, _ := listValue(v.allocator, v.value)
112+
if list != nil {
113+
defer v.allocator.Free(list)
114+
}
111115
if t.ElementRelationship == schema.Atomic {
112116
v.set.Insert(v.path)
113117
return nil
@@ -143,9 +147,9 @@ func (v *toFieldSetWalker) visitMapItems(t *schema.Map, m value.Map) (errs Valid
143147
}
144148

145149
func (v *toFieldSetWalker) doMap(t *schema.Map) (errs ValidationErrors) {
146-
m, _ := mapValue(v.value)
150+
m, _ := mapValue(v.allocator, v.value)
147151
if m != nil {
148-
defer m.Recycle()
152+
defer v.allocator.Free(m)
149153
}
150154
if t.ElementRelationship == schema.Atomic {
151155
v.set.Insert(v.path)

typed/typed.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,9 @@ func merge(lhs, rhs *TypedValue, rule, postRule mergeRule) (*TypedValue, error)
238238
mw.typeRef = lhs.typeRef
239239
mw.rule = rule
240240
mw.postItemHook = postRule
241+
if mw.allocator == nil {
242+
mw.allocator = value.NewFreelistAllocator()
243+
}
241244

242245
errs := mw.merge(nil)
243246
if len(errs) > 0 {

0 commit comments

Comments
 (0)