Skip to content

Commit e160667

Browse files
joeybloggsjoeybloggs
authored andcommitted
Merge branch 'minor-perf-3'
2 parents 206e285 + 0c37d20 commit e160667

File tree

8 files changed

+171
-124
lines changed

8 files changed

+171
-124
lines changed

README.md

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Package form
22
============
33
<img align="right" src="https://raw.githubusercontent.com/go-playground/form/master/logo.jpg">
4-
![Project status](https://img.shields.io/badge/version-1.8.0-green.svg)
4+
![Project status](https://img.shields.io/badge/version-1.9.0-green.svg)
55
[![Build Status](https://semaphoreci.com/api/v1/joeybloggs/form/branches/master/badge.svg)](https://semaphoreci.com/joeybloggs/form)
66
[![Coverage Status](https://coveralls.io/repos/github/go-playground/form/badge.svg?branch=master)](https://coveralls.io/github/go-playground/form?branch=master)
77
[![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/form)](https://goreportcard.com/report/github.com/go-playground/form)
@@ -268,26 +268,26 @@ NOTE: the 1 allocation and B/op in the first 4 decodes is actually the struct al
268268
go test -bench=. -benchmem=true
269269

270270
PASS
271-
BenchmarkSimpleUserDecodeStruct-8 5000000 319 ns/op 64 B/op 1 allocs/op
272-
BenchmarkSimpleUserDecodeStructParallel-8 20000000 116 ns/op 64 B/op 1 allocs/op
273-
BenchmarkSimpleUserEncodeStruct-8 1000000 1015 ns/op 549 B/op 12 allocs/op
274-
BenchmarkSimpleUserEncodeStructParallel-8 5000000 411 ns/op 549 B/op 12 allocs/op
275-
BenchmarkPrimitivesDecodeStructAllPrimitivesTypes-8 1000000 1038 ns/op 96 B/op 1 allocs/op
276-
BenchmarkPrimitivesDecodeStructAllPrimitivesTypesParallel-8 5000000 368 ns/op 96 B/op 1 allocs/op
277-
BenchmarkPrimitivesEncodeStructAllPrimitivesTypes-8 200000 5014 ns/op 3073 B/op 47 allocs/op
278-
BenchmarkPrimitivesEncodeStructAllPrimitivesTypesParallel-8 1000000 1973 ns/op 3072 B/op 47 allocs/op
279-
BenchmarkComplexArrayDecodeStructAllTypes-8 100000 17656 ns/op 2515 B/op 123 allocs/op
280-
BenchmarkComplexArrayDecodeStructAllTypesParallel-8 300000 5801 ns/op 2518 B/op 123 allocs/op
281-
BenchmarkComplexArrayEncodeStructAllTypes-8 100000 16318 ns/op 7351 B/op 147 allocs/op
282-
BenchmarkComplexArrayEncodeStructAllTypesParallel-8 300000 5955 ns/op 7351 B/op 147 allocs/op
283-
BenchmarkComplexMapDecodeStructAllTypes-8 50000 25196 ns/op 7089 B/op 135 allocs/op
284-
BenchmarkComplexMapDecodeStructAllTypesParallel-8 200000 9443 ns/op 7123 B/op 135 allocs/op
285-
BenchmarkComplexMapEncodeStructAllTypes-8 100000 17061 ns/op 7162 B/op 176 allocs/op
286-
BenchmarkComplexMapEncodeStructAllTypesParallel-8 300000 6375 ns/op 7160 B/op 176 allocs/op
287-
BenchmarkDecodeNestedStruct-8 300000 4102 ns/op 640 B/op 16 allocs/op
288-
BenchmarkDecodeNestedStructParallel-8 1000000 1399 ns/op 640 B/op 16 allocs/op
289-
BenchmarkEncodeNestedStruct-8 500000 2440 ns/op 768 B/op 17 allocs/op
290-
BenchmarkEncodeNestedStructParallel-8 2000000 1045 ns/op 768 B/op 17 allocs/op
271+
BenchmarkSimpleUserDecodeStruct-8 5000000 308 ns/op 64 B/op 1 allocs/op
272+
BenchmarkSimpleUserDecodeStructParallel-8 20000000 94.8 ns/op 64 B/op 1 allocs/op
273+
BenchmarkSimpleUserEncodeStruct-8 2000000 989 ns/op 549 B/op 12 allocs/op
274+
BenchmarkSimpleUserEncodeStructParallel-8 5000000 332 ns/op 549 B/op 12 allocs/op
275+
BenchmarkPrimitivesDecodeStructAllPrimitivesTypes-8 1000000 1004 ns/op 96 B/op 1 allocs/op
276+
BenchmarkPrimitivesDecodeStructAllPrimitivesTypesParallel-8 5000000 291 ns/op 96 B/op 1 allocs/op
277+
BenchmarkPrimitivesEncodeStructAllPrimitivesTypes-8 300000 4771 ns/op 3073 B/op 47 allocs/op
278+
BenchmarkPrimitivesEncodeStructAllPrimitivesTypesParallel-8 1000000 1575 ns/op 3073 B/op 47 allocs/op
279+
BenchmarkComplexArrayDecodeStructAllTypes-8 100000 17087 ns/op 2513 B/op 123 allocs/op
280+
BenchmarkComplexArrayDecodeStructAllTypesParallel-8 300000 5020 ns/op 2518 B/op 123 allocs/op
281+
BenchmarkComplexArrayEncodeStructAllTypes-8 100000 16219 ns/op 7350 B/op 147 allocs/op
282+
BenchmarkComplexArrayEncodeStructAllTypesParallel-8 300000 4961 ns/op 7351 B/op 147 allocs/op
283+
BenchmarkComplexMapDecodeStructAllTypes-8 50000 24898 ns/op 7088 B/op 135 allocs/op
284+
BenchmarkComplexMapDecodeStructAllTypesParallel-8 200000 7771 ns/op 7121 B/op 135 allocs/op
285+
BenchmarkComplexMapEncodeStructAllTypes-8 100000 16885 ns/op 7159 B/op 176 allocs/op
286+
BenchmarkComplexMapEncodeStructAllTypesParallel-8 300000 5851 ns/op 7161 B/op 176 allocs/op
287+
BenchmarkDecodeNestedStruct-8 300000 3848 ns/op 640 B/op 16 allocs/op
288+
BenchmarkDecodeNestedStructParallel-8 1000000 1325 ns/op 640 B/op 16 allocs/op
289+
BenchmarkEncodeNestedStruct-8 500000 2319 ns/op 768 B/op 17 allocs/op
290+
BenchmarkEncodeNestedStructParallel-8 2000000 874 ns/op 768 B/op 17 allocs/op
291291
```
292292

293293
Competitor benchmarks can be found [here](https://github.com/go-playground/form/blob/master/benchmarks/benchmarks.md)

benchmarks/benchmarks.md

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,27 @@
22

33
### go-playground/form
44
```go
5-
BenchmarkSimpleUserDecodeStruct-8 5000000 319 ns/op 64 B/op 1 allocs/op
6-
BenchmarkSimpleUserDecodeStructParallel-8 20000000 116 ns/op 64 B/op 1 allocs/op
7-
BenchmarkSimpleUserEncodeStruct-8 1000000 1015 ns/op 549 B/op 12 allocs/op
8-
BenchmarkSimpleUserEncodeStructParallel-8 5000000 411 ns/op 549 B/op 12 allocs/op
9-
BenchmarkPrimitivesDecodeStructAllPrimitivesTypes-8 1000000 1038 ns/op 96 B/op 1 allocs/op
10-
BenchmarkPrimitivesDecodeStructAllPrimitivesTypesParallel-8 5000000 368 ns/op 96 B/op 1 allocs/op
11-
BenchmarkPrimitivesEncodeStructAllPrimitivesTypes-8 200000 5014 ns/op 3073 B/op 47 allocs/op
12-
BenchmarkPrimitivesEncodeStructAllPrimitivesTypesParallel-8 1000000 1973 ns/op 3072 B/op 47 allocs/op
13-
BenchmarkComplexArrayDecodeStructAllTypes-8 100000 17656 ns/op 2515 B/op 123 allocs/op
14-
BenchmarkComplexArrayDecodeStructAllTypesParallel-8 300000 5801 ns/op 2518 B/op 123 allocs/op
15-
BenchmarkComplexArrayEncodeStructAllTypes-8 100000 16318 ns/op 7351 B/op 147 allocs/op
16-
BenchmarkComplexArrayEncodeStructAllTypesParallel-8 300000 5955 ns/op 7351 B/op 147 allocs/op
17-
BenchmarkComplexMapDecodeStructAllTypes-8 50000 25196 ns/op 7089 B/op 135 allocs/op
18-
BenchmarkComplexMapDecodeStructAllTypesParallel-8 200000 9443 ns/op 7123 B/op 135 allocs/op
19-
BenchmarkComplexMapEncodeStructAllTypes-8 100000 17061 ns/op 7162 B/op 176 allocs/op
20-
BenchmarkComplexMapEncodeStructAllTypesParallel-8 300000 6375 ns/op 7160 B/op 176 allocs/op
21-
BenchmarkDecodeNestedStruct-8 300000 4102 ns/op 640 B/op 16 allocs/op
22-
BenchmarkDecodeNestedStructParallel-8 1000000 1399 ns/op 640 B/op 16 allocs/op
23-
BenchmarkEncodeNestedStruct-8 500000 2440 ns/op 768 B/op 17 allocs/op
24-
BenchmarkEncodeNestedStructParallel-8 2000000 1045 ns/op 768 B/op 17 allocs/op
5+
PASS
6+
BenchmarkSimpleUserDecodeStruct-8 5000000 308 ns/op 64 B/op 1 allocs/op
7+
BenchmarkSimpleUserDecodeStructParallel-8 20000000 94.8 ns/op 64 B/op 1 allocs/op
8+
BenchmarkSimpleUserEncodeStruct-8 2000000 989 ns/op 549 B/op 12 allocs/op
9+
BenchmarkSimpleUserEncodeStructParallel-8 5000000 332 ns/op 549 B/op 12 allocs/op
10+
BenchmarkPrimitivesDecodeStructAllPrimitivesTypes-8 1000000 1004 ns/op 96 B/op 1 allocs/op
11+
BenchmarkPrimitivesDecodeStructAllPrimitivesTypesParallel-8 5000000 291 ns/op 96 B/op 1 allocs/op
12+
BenchmarkPrimitivesEncodeStructAllPrimitivesTypes-8 300000 4771 ns/op 3073 B/op 47 allocs/op
13+
BenchmarkPrimitivesEncodeStructAllPrimitivesTypesParallel-8 1000000 1575 ns/op 3073 B/op 47 allocs/op
14+
BenchmarkComplexArrayDecodeStructAllTypes-8 100000 17087 ns/op 2513 B/op 123 allocs/op
15+
BenchmarkComplexArrayDecodeStructAllTypesParallel-8 300000 5020 ns/op 2518 B/op 123 allocs/op
16+
BenchmarkComplexArrayEncodeStructAllTypes-8 100000 16219 ns/op 7350 B/op 147 allocs/op
17+
BenchmarkComplexArrayEncodeStructAllTypesParallel-8 300000 4961 ns/op 7351 B/op 147 allocs/op
18+
BenchmarkComplexMapDecodeStructAllTypes-8 50000 24898 ns/op 7088 B/op 135 allocs/op
19+
BenchmarkComplexMapDecodeStructAllTypesParallel-8 200000 7771 ns/op 7121 B/op 135 allocs/op
20+
BenchmarkComplexMapEncodeStructAllTypes-8 100000 16885 ns/op 7159 B/op 176 allocs/op
21+
BenchmarkComplexMapEncodeStructAllTypesParallel-8 300000 5851 ns/op 7161 B/op 176 allocs/op
22+
BenchmarkDecodeNestedStruct-8 300000 3848 ns/op 640 B/op 16 allocs/op
23+
BenchmarkDecodeNestedStructParallel-8 1000000 1325 ns/op 640 B/op 16 allocs/op
24+
BenchmarkEncodeNestedStruct-8 500000 2319 ns/op 768 B/op 17 allocs/op
25+
BenchmarkEncodeNestedStructParallel-8 2000000 874 ns/op 768 B/op 17 allocs/op
2526
```
2627

2728
### gorilla/schema

cache.go

Lines changed: 66 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package form
33
import (
44
"reflect"
55
"sync"
6+
"sync/atomic"
67
)
78

89
type cachedField struct {
@@ -15,19 +16,76 @@ type cachedStruct struct {
1516
}
1617

1718
type structCacheMap struct {
18-
lock sync.RWMutex
19-
m map[reflect.Type]cachedStruct
19+
m atomic.Value // map[reflect.Type]*cachedStruct
20+
lock sync.Mutex
2021
}
2122

22-
func (s *structCacheMap) Get(key reflect.Type) (value cachedStruct, ok bool) {
23-
s.lock.RLock()
24-
value, ok = s.m[key]
25-
s.lock.RUnlock()
23+
func newStructCacheMap() *structCacheMap {
24+
sc := new(structCacheMap)
25+
sc.m.Store(map[reflect.Type]*cachedStruct{})
26+
27+
return sc
28+
}
29+
30+
func (s *structCacheMap) Get(key reflect.Type) (value *cachedStruct, ok bool) {
31+
value, ok = s.m.Load().(map[reflect.Type]*cachedStruct)[key]
2632
return
2733
}
2834

29-
func (s *structCacheMap) Set(key reflect.Type, value cachedStruct) {
35+
func (s *structCacheMap) Set(key reflect.Type, value *cachedStruct) {
36+
37+
m := s.m.Load().(map[reflect.Type]*cachedStruct)
38+
39+
nm := make(map[reflect.Type]*cachedStruct, len(m)+1)
40+
for k, v := range m {
41+
nm[k] = v
42+
}
43+
nm[key] = value
44+
s.m.Store(nm)
45+
}
46+
47+
func (s *structCacheMap) parseStruct(current reflect.Value, key reflect.Type, tagName string) *cachedStruct {
48+
3049
s.lock.Lock()
31-
s.m[key] = value
50+
51+
// could have been multiple trying to access, but once first is done this ensures struct
52+
// isn't parsed again.
53+
cs, ok := s.Get(key)
54+
if ok {
55+
s.lock.Unlock()
56+
return cs
57+
}
58+
59+
typ := current.Type()
60+
cs = &cachedStruct{fields: make([]cachedField, 0, 4)} // init 4, betting most structs decoding into have at aleast 4 fields.
61+
62+
numFields := current.NumField()
63+
64+
var fld reflect.StructField
65+
var name string
66+
67+
for i := 0; i < numFields; i++ {
68+
69+
fld = typ.Field(i)
70+
71+
if fld.PkgPath != blank && !fld.Anonymous {
72+
continue
73+
}
74+
75+
if name = fld.Tag.Get(tagName); name == ignore {
76+
continue
77+
}
78+
79+
if len(name) == 0 {
80+
name = fld.Name
81+
}
82+
83+
cs.fields = append(cs.fields, cachedField{idx: i, name: name})
84+
}
85+
86+
s.Set(typ, cs)
87+
3288
s.lock.Unlock()
89+
90+
return cs
3391
}

cache_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package form
2+
3+
import (
4+
"reflect"
5+
"testing"
6+
7+
. "gopkg.in/go-playground/assert.v1"
8+
)
9+
10+
// NOTES:
11+
// - Run "go test" to run tests
12+
// - Run "gocov test | gocov report" to report on test converage by file
13+
// - Run "gocov test | gocov annotate -" to report on all code and functions, those ,marked with "MISS" were never called
14+
//
15+
// or
16+
//
17+
// -- may be a good idea to change to output path to somewherelike /tmp
18+
// go test -coverprofile cover.out && go tool cover -html=cover.out -o cover.html
19+
//
20+
//
21+
// go test -cpuprofile cpu.out
22+
// ./validator.test -test.bench=. -test.cpuprofile=cpu.prof
23+
// go tool pprof validator.test cpu.prof
24+
//
25+
//
26+
// go test -memprofile mem.out
27+
28+
func TestDecoderMultipleSimultaniousParseStructRequests(t *testing.T) {
29+
30+
sc := newStructCacheMap()
31+
32+
type Struct struct {
33+
Array []int
34+
}
35+
36+
proceed := make(chan struct{})
37+
38+
var test Struct
39+
40+
sv := reflect.ValueOf(test)
41+
typ := sv.Type()
42+
43+
for i := 0; i < 200; i++ {
44+
go func() {
45+
<-proceed
46+
s := sc.parseStruct(sv, typ, "form")
47+
NotEqual(t, s, nil)
48+
}()
49+
}
50+
51+
close(proceed)
52+
}

decoder.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,8 @@ func (d *decoder) traverseStruct(v reflect.Value, namespace []byte) (set bool) {
134134
namespace = namespace[:l]
135135
fld = typ.Field(i)
136136

137-
if fld.PkgPath != blank && !fld.Anonymous {
137+
// if unexposed field
138+
if !fld.Anonymous && fld.PkgPath != blank {
138139
continue
139140
}
140141

@@ -159,9 +160,10 @@ func (d *decoder) traverseStruct(v reflect.Value, namespace []byte) (set bool) {
159160

160161
}
161162
} else {
163+
162164
s, ok := d.d.structCache.Get(typ)
163165
if !ok {
164-
s = d.d.parseStruct(v)
166+
s = d.d.structCache.parseStruct(v, typ, d.d.tagName)
165167
}
166168

167169
for _, f := range s.fields {

encoder.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ func (e *encoder) traverseStruct(v reflect.Value, namespace []byte, idx int) {
7979
} else {
8080
s, ok := e.e.structCache.Get(typ)
8181
if !ok {
82-
s = e.e.parseStruct(v)
82+
s = e.e.structCache.parseStruct(v, typ, e.e.tagName)
8383
}
8484

8585
for _, f := range s.fields {

form_decoder.go

Lines changed: 3 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -44,17 +44,18 @@ type dataMap map[string]*recursiveData
4444
// Decoder is the main decode instance
4545
type Decoder struct {
4646
tagName string
47-
structCache structCacheMap
47+
structCache *structCacheMap
4848
customTypeFuncs map[reflect.Type]DecodeCustomTypeFunc
4949
maxArraySize int
5050
keyPool *sync.Pool
5151
}
5252

5353
// NewDecoder creates a new decoder instance with sane defaults
5454
func NewDecoder() *Decoder {
55+
5556
return &Decoder{
5657
tagName: "form",
57-
structCache: structCacheMap{m: map[reflect.Type]cachedStruct{}},
58+
structCache: newStructCacheMap(),
5859
maxArraySize: 10000,
5960
keyPool: &sync.Pool{New: func() interface{} {
6061
return &recursiveData{
@@ -92,40 +93,6 @@ func (d *Decoder) RegisterCustomTypeFunc(fn DecodeCustomTypeFunc, types ...inter
9293
}
9394
}
9495

95-
func (d *Decoder) parseStruct(current reflect.Value) cachedStruct {
96-
97-
typ := current.Type()
98-
s := cachedStruct{fields: make([]cachedField, 0, 4)} // init 4, betting most structs decoding into have at aleast 4 fields.
99-
100-
numFields := current.NumField()
101-
102-
var fld reflect.StructField
103-
var name string
104-
105-
for i := 0; i < numFields; i++ {
106-
107-
fld = typ.Field(i)
108-
109-
if fld.PkgPath != blank && !fld.Anonymous {
110-
continue
111-
}
112-
113-
if name = fld.Tag.Get(d.tagName); name == ignore {
114-
continue
115-
}
116-
117-
if len(name) == 0 {
118-
name = fld.Name
119-
}
120-
121-
s.fields = append(s.fields, cachedField{idx: i, name: name})
122-
}
123-
124-
d.structCache.Set(typ, s)
125-
126-
return s
127-
}
128-
12996
// Decode decodes the given values and sets the corresponding struct values
13097
func (d *Decoder) Decode(v interface{}, values url.Values) (err error) {
13198

form_encoder.go

Lines changed: 3 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,16 @@ func (e EncodeErrors) Error() string {
3030
// Encoder is the main encode instance
3131
type Encoder struct {
3232
tagName string
33-
structCache structCacheMap
33+
structCache *structCacheMap
3434
customTypeFuncs map[reflect.Type]EncodeCustomTypeFunc
3535
}
3636

3737
// NewEncoder creates a new encoder instance with sane defaults
3838
func NewEncoder() *Encoder {
39+
3940
return &Encoder{
4041
tagName: "form",
41-
structCache: structCacheMap{m: map[reflect.Type]cachedStruct{}},
42+
structCache: newStructCacheMap(),
4243
}
4344
}
4445

@@ -83,37 +84,3 @@ func (e *Encoder) Encode(v interface{}) (url.Values, error) {
8384

8485
return enc.values, enc.errs
8586
}
86-
87-
func (e *Encoder) parseStruct(current reflect.Value) cachedStruct {
88-
89-
typ := current.Type()
90-
s := cachedStruct{fields: make([]cachedField, 0, 4)}
91-
92-
numFields := current.NumField()
93-
94-
var fld reflect.StructField
95-
var name string
96-
97-
for i := 0; i < numFields; i++ {
98-
99-
fld = typ.Field(i)
100-
101-
if fld.PkgPath != blank && !fld.Anonymous {
102-
continue
103-
}
104-
105-
if name = fld.Tag.Get(e.tagName); name == ignore {
106-
continue
107-
}
108-
109-
if len(name) == 0 {
110-
name = fld.Name
111-
}
112-
113-
s.fields = append(s.fields, cachedField{idx: i, name: name})
114-
}
115-
116-
e.structCache.Set(typ, s)
117-
118-
return s
119-
}

0 commit comments

Comments
 (0)