Skip to content

Commit

Permalink
use atomic.Value to improve parallel performance by up to 20%
Browse files Browse the repository at this point in the history
  • Loading branch information
deankarn committed Jul 9, 2016
1 parent 206e285 commit 3267fdf
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 19 deletions.
25 changes: 14 additions & 11 deletions cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package form

import (
"reflect"
"sync"
"sync/atomic"
)

type cachedField struct {
Expand All @@ -15,19 +15,22 @@ type cachedStruct struct {
}

type structCacheMap struct {
lock sync.RWMutex
m map[reflect.Type]cachedStruct
m atomic.Value // map[reflect.Type]*cachedStruct
}

func (s *structCacheMap) Get(key reflect.Type) (value cachedStruct, ok bool) {
s.lock.RLock()
value, ok = s.m[key]
s.lock.RUnlock()
func (s *structCacheMap) Get(key reflect.Type) (value *cachedStruct, ok bool) {
value, ok = s.m.Load().(map[reflect.Type]*cachedStruct)[key]
return
}

func (s *structCacheMap) Set(key reflect.Type, value cachedStruct) {
s.lock.Lock()
s.m[key] = value
s.lock.Unlock()
func (s *structCacheMap) Set(key reflect.Type, value *cachedStruct) {

m := s.m.Load().(map[reflect.Type]*cachedStruct)

nm := make(map[reflect.Type]*cachedStruct, len(m)+1)
for k, v := range m {
nm[k] = v
}
nm[key] = value
s.m.Store(nm)
}
3 changes: 2 additions & 1 deletion decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,10 @@ func (d *decoder) traverseStruct(v reflect.Value, namespace []byte) (set bool) {

}
} else {

s, ok := d.d.structCache.Get(typ)
if !ok {
s = d.d.parseStruct(v)
s = d.d.parseStruct(v, typ)
}

for _, f := range s.fields {
Expand Down
2 changes: 1 addition & 1 deletion encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func (e *encoder) traverseStruct(v reflect.Value, namespace []byte, idx int) {
} else {
s, ok := e.e.structCache.Get(typ)
if !ok {
s = e.e.parseStruct(v)
s = e.e.parseStruct(v, typ)
}

for _, f := range s.fields {
Expand Down
23 changes: 20 additions & 3 deletions form_decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,18 @@ type Decoder struct {
customTypeFuncs map[reflect.Type]DecodeCustomTypeFunc
maxArraySize int
keyPool *sync.Pool
cacheLock sync.Mutex
}

// NewDecoder creates a new decoder instance with sane defaults
func NewDecoder() *Decoder {

sc := structCacheMap{}
sc.m.Store(map[reflect.Type]*cachedStruct{})

return &Decoder{
tagName: "form",
structCache: structCacheMap{m: map[reflect.Type]cachedStruct{}},
structCache: sc,
maxArraySize: 10000,
keyPool: &sync.Pool{New: func() interface{} {
return &recursiveData{
Expand Down Expand Up @@ -92,10 +97,20 @@ func (d *Decoder) RegisterCustomTypeFunc(fn DecodeCustomTypeFunc, types ...inter
}
}

func (d *Decoder) parseStruct(current reflect.Value) cachedStruct {
func (d *Decoder) parseStruct(current reflect.Value, key reflect.Type) *cachedStruct {

d.cacheLock.Lock()

// could have been multiple trying to access, but once first is done this ensures struct
// isn;t parsed again.
s, ok := d.structCache.Get(key)
if ok {
d.cacheLock.Unlock()
return s
}

typ := current.Type()
s := cachedStruct{fields: make([]cachedField, 0, 4)} // init 4, betting most structs decoding into have at aleast 4 fields.
s = &cachedStruct{fields: make([]cachedField, 0, 4)} // init 4, betting most structs decoding into have at aleast 4 fields.

numFields := current.NumField()

Expand Down Expand Up @@ -123,6 +138,8 @@ func (d *Decoder) parseStruct(current reflect.Value) cachedStruct {

d.structCache.Set(typ, s)

d.cacheLock.Unlock()

return s
}

Expand Down
24 changes: 21 additions & 3 deletions form_encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"net/url"
"reflect"
"strings"
"sync"
)

// EncodeCustomTypeFunc allows for registering/overriding types to be parsed.
Expand Down Expand Up @@ -32,13 +33,18 @@ type Encoder struct {
tagName string
structCache structCacheMap
customTypeFuncs map[reflect.Type]EncodeCustomTypeFunc
cacheLock sync.Mutex
}

// NewEncoder creates a new encoder instance with sane defaults
func NewEncoder() *Encoder {

sc := structCacheMap{}
sc.m.Store(map[reflect.Type]*cachedStruct{})

return &Encoder{
tagName: "form",
structCache: structCacheMap{m: map[reflect.Type]cachedStruct{}},
structCache: sc,
}
}

Expand Down Expand Up @@ -84,10 +90,20 @@ func (e *Encoder) Encode(v interface{}) (url.Values, error) {
return enc.values, enc.errs
}

func (e *Encoder) parseStruct(current reflect.Value) cachedStruct {
func (e *Encoder) parseStruct(current reflect.Value, key reflect.Type) *cachedStruct {

e.cacheLock.Lock()

// could have been multiple trying to access, but once first is done this ensures struct
// isn;t parsed again.
s, ok := e.structCache.Get(key)
if ok {
e.cacheLock.Unlock()
return s
}

typ := current.Type()
s := cachedStruct{fields: make([]cachedField, 0, 4)}
s = &cachedStruct{fields: make([]cachedField, 0, 4)}

numFields := current.NumField()

Expand Down Expand Up @@ -115,5 +131,7 @@ func (e *Encoder) parseStruct(current reflect.Value) cachedStruct {

e.structCache.Set(typ, s)

e.cacheLock.Unlock()

return s
}

0 comments on commit 3267fdf

Please sign in to comment.