Skip to content

Commit 5d64b2b

Browse files
authored
refactor: cache reflection result in CBOR generic encode/decode (#796)
1 parent bf6d8d7 commit 5d64b2b

File tree

2 files changed

+61
-28
lines changed

2 files changed

+61
-28
lines changed

cbor/decode.go

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"bytes"
1919
"fmt"
2020
"reflect"
21+
"sync"
2122

2223
_cbor "github.com/fxamacker/cbor/v2"
2324
"github.com/jinzhu/copier"
@@ -109,27 +110,43 @@ func DecodeById(
109110
return ret, nil
110111
}
111112

113+
var decodeGenericTypeCache = map[reflect.Type]reflect.Type{}
114+
var decodeGenericTypeCacheMutex sync.RWMutex
115+
112116
// DecodeGeneric decodes the specified CBOR into the destination object without using the
113117
// destination object's UnmarshalCBOR() function
114118
func DecodeGeneric(cborData []byte, dest interface{}) error {
115-
// Create a duplicate(-ish) struct from the destination
116-
// We do this so that we can bypass any custom UnmarshalCBOR() function on the
117-
// destination object
119+
// Get destination type
118120
valueDest := reflect.ValueOf(dest)
119-
if valueDest.Kind() != reflect.Pointer ||
120-
valueDest.Elem().Kind() != reflect.Struct {
121-
return fmt.Errorf("destination must be a pointer to a struct")
122-
}
123-
typeDestElem := valueDest.Elem().Type()
124-
destTypeFields := []reflect.StructField{}
125-
for i := 0; i < typeDestElem.NumField(); i++ {
126-
tmpField := typeDestElem.Field(i)
127-
if tmpField.IsExported() && tmpField.Name != "DecodeStoreCbor" {
128-
destTypeFields = append(destTypeFields, tmpField)
121+
typeDest := valueDest.Elem().Type()
122+
// Check type cache
123+
decodeGenericTypeCacheMutex.RLock()
124+
tmpTypeDest, ok := decodeGenericTypeCache[typeDest]
125+
decodeGenericTypeCacheMutex.RUnlock()
126+
if !ok {
127+
// Create a duplicate(-ish) struct from the destination
128+
// We do this so that we can bypass any custom UnmarshalCBOR() function on the
129+
// destination object
130+
if valueDest.Kind() != reflect.Pointer ||
131+
valueDest.Elem().Kind() != reflect.Struct {
132+
decodeGenericTypeCacheMutex.Unlock()
133+
return fmt.Errorf("destination must be a pointer to a struct")
134+
}
135+
destTypeFields := []reflect.StructField{}
136+
for i := 0; i < typeDest.NumField(); i++ {
137+
tmpField := typeDest.Field(i)
138+
if tmpField.IsExported() && tmpField.Name != "DecodeStoreCbor" {
139+
destTypeFields = append(destTypeFields, tmpField)
140+
}
129141
}
142+
tmpTypeDest = reflect.StructOf(destTypeFields)
143+
// Populate cache
144+
decodeGenericTypeCacheMutex.Lock()
145+
decodeGenericTypeCache[typeDest] = tmpTypeDest
146+
decodeGenericTypeCacheMutex.Unlock()
130147
}
131148
// Create temporary object with the type created above
132-
tmpDest := reflect.New(reflect.StructOf(destTypeFields))
149+
tmpDest := reflect.New(tmpTypeDest)
133150
// Decode CBOR into temporary object
134151
if _, err := Decode(cborData, tmpDest.Interface()); err != nil {
135152
return err

cbor/encode.go

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"bytes"
1919
"fmt"
2020
"reflect"
21+
"sync"
2122

2223
_cbor "github.com/fxamacker/cbor/v2"
2324
"github.com/jinzhu/copier"
@@ -38,27 +39,42 @@ func Encode(data interface{}) ([]byte, error) {
3839
return buf.Bytes(), err
3940
}
4041

42+
var encodeGenericTypeCache = map[reflect.Type]reflect.Type{}
43+
var encodeGenericTypeCacheMutex sync.RWMutex
44+
4145
// EncodeGeneric encodes the specified object to CBOR without using the source object's
4246
// MarshalCBOR() function
4347
func EncodeGeneric(src interface{}) ([]byte, error) {
44-
// Create a duplicate(-ish) struct from the destination
45-
// We do this so that we can bypass any custom UnmarshalCBOR() function on the
46-
// destination object
48+
// Get source type
4749
valueSrc := reflect.ValueOf(src)
48-
if valueSrc.Kind() != reflect.Pointer ||
49-
valueSrc.Elem().Kind() != reflect.Struct {
50-
return nil, fmt.Errorf("source must be a pointer to a struct")
51-
}
52-
typeSrcElem := valueSrc.Elem().Type()
53-
srcTypeFields := []reflect.StructField{}
54-
for i := 0; i < typeSrcElem.NumField(); i++ {
55-
tmpField := typeSrcElem.Field(i)
56-
if tmpField.IsExported() && tmpField.Name != "DecodeStoreCbor" {
57-
srcTypeFields = append(srcTypeFields, tmpField)
50+
typeSrc := valueSrc.Elem().Type()
51+
// Check type cache
52+
encodeGenericTypeCacheMutex.RLock()
53+
tmpTypeSrc, ok := encodeGenericTypeCache[typeSrc]
54+
encodeGenericTypeCacheMutex.RUnlock()
55+
if !ok {
56+
// Create a duplicate(-ish) struct from the destination
57+
// We do this so that we can bypass any custom MarshalCBOR() function on the
58+
// source object
59+
if valueSrc.Kind() != reflect.Pointer ||
60+
valueSrc.Elem().Kind() != reflect.Struct {
61+
return nil, fmt.Errorf("source must be a pointer to a struct")
62+
}
63+
srcTypeFields := []reflect.StructField{}
64+
for i := 0; i < typeSrc.NumField(); i++ {
65+
tmpField := typeSrc.Field(i)
66+
if tmpField.IsExported() && tmpField.Name != "DecodeStoreCbor" {
67+
srcTypeFields = append(srcTypeFields, tmpField)
68+
}
5869
}
70+
tmpTypeSrc = reflect.StructOf(srcTypeFields)
71+
// Populate cache
72+
encodeGenericTypeCacheMutex.Lock()
73+
encodeGenericTypeCache[typeSrc] = tmpTypeSrc
74+
encodeGenericTypeCacheMutex.Unlock()
5975
}
6076
// Create temporary object with the type created above
61-
tmpSrc := reflect.New(reflect.StructOf(srcTypeFields))
77+
tmpSrc := reflect.New(tmpTypeSrc)
6278
// Copy values from source object into temporary object
6379
if err := copier.Copy(tmpSrc.Interface(), src); err != nil {
6480
return nil, err

0 commit comments

Comments
 (0)