Skip to content
This repository was archived by the owner on Dec 23, 2025. It is now read-only.

Commit 6265a5b

Browse files
committed
chore(binding): Improve performance
Change-Id: I0f5083700848a87eff617ae2a391070af83eddbc
1 parent b46ca7f commit 6265a5b

File tree

6 files changed

+139
-37
lines changed

6 files changed

+139
-37
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ _testmain.go
2323
*.cfg
2424
*.pptx
2525
*.log
26-
*nohup.out
26+
*.out
2727
*.sublime-project
2828
*.sublime-workspace
2929
.DS_Store

binding/bind.go

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ package binding
33
import (
44
"net/http"
55
"reflect"
6+
"sync"
67
_ "unsafe"
78

89
"github.com/bytedance/go-tagexpr"
910
"github.com/bytedance/go-tagexpr/validator"
10-
"github.com/henrylee2cn/goutil"
1111
"github.com/henrylee2cn/goutil/tpack"
1212
)
1313

@@ -27,7 +27,8 @@ const (
2727
type Binding struct {
2828
level Level
2929
vd *validator.Validator
30-
recvs goutil.Map
30+
recvs map[int32]*receiver
31+
lock sync.RWMutex
3132
bindErrFactory func(failField, msg string) error
3233
}
3334

@@ -40,7 +41,7 @@ func New(tagName string) *Binding {
4041
}
4142
b := &Binding{
4243
vd: validator.New(tagName),
43-
recvs: goutil.AtomicMap(),
44+
recvs: make(map[int32]*receiver, 1024),
4445
}
4546
return b.SetLevel(FirstAndTagged).SetErrorFactory(nil, nil)
4647
}
@@ -124,16 +125,18 @@ func (b *Binding) structValueOf(structPointer interface{}) (reflect.Value, error
124125

125126
func (b *Binding) getObjOrPrepare(value reflect.Value) (*receiver, error) {
126127
runtimeTypeID := tpack.From(value).RuntimeTypeID()
127-
i, ok := b.recvs.Load(runtimeTypeID)
128+
b.lock.RLock()
129+
recv, ok := b.recvs[runtimeTypeID]
130+
b.lock.RUnlock()
128131
if ok {
129-
return i.(*receiver), nil
132+
return recv, nil
130133
}
131134

132135
expr, err := b.vd.VM().Run(reflect.New(value.Type()).Elem())
133136
if err != nil {
134137
return nil, err
135138
}
136-
var recv = &receiver{
139+
recv = &receiver{
137140
params: make([]*paramInfo, 0, 16),
138141
}
139142
var errExprSelector tagexpr.ExprSelector
@@ -239,7 +242,10 @@ func (b *Binding) getObjOrPrepare(value reflect.Value) (*receiver, error) {
239242

240243
recv.initParams()
241244

242-
b.recvs.Store(runtimeTypeID, recv)
245+
b.lock.Lock()
246+
b.recvs[runtimeTypeID] = recv
247+
b.lock.Unlock()
248+
243249
return recv, nil
244250
}
245251

@@ -256,7 +262,7 @@ func (b *Binding) bind(value reflect.Value, req *http.Request, pathParams PathPa
256262

257263
bodyCodec := recv.getBodyCodec(req)
258264

259-
bodyBytes, err := recv.getBodyBytes(req, bodyCodec == jsonBody)
265+
bodyBytes, bodyString, err := recv.getBody(req, bodyCodec == jsonBody)
260266
if err != nil {
261267
return false, err
262268
}
@@ -280,12 +286,24 @@ func (b *Binding) bind(value reflect.Value, req *http.Request, pathParams PathPa
280286
case cookie:
281287
err = param.bindCookie(expr, cookies)
282288
case body:
283-
_, err = param.bindBody(expr, bodyCodec, postForm, bodyBytes)
289+
switch bodyCodec {
290+
case formBody:
291+
_, err = param.bindMapStrings(expr, postForm)
292+
case jsonBody:
293+
_, err = param.bindJSON(expr, bodyString)
294+
default:
295+
err = param.contentTypeError
296+
}
284297
case raw_body:
285298
err = param.bindRawBody(expr, bodyBytes)
286299
default:
287300
var found bool
288-
found, err = param.bindBody(expr, bodyCodec, postForm, bodyBytes)
301+
switch bodyCodec {
302+
case formBody:
303+
found, err = param.bindMapStrings(expr, postForm)
304+
case jsonBody:
305+
found, err = param.bindJSON(expr, bodyString)
306+
}
289307
if !found {
290308
_, err = param.bindQuery(expr, queryValues)
291309
}

binding/bind_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package binding_test
22

33
import (
44
"bytes"
5+
"encoding/json"
56
"io"
67
"io/ioutil"
78
"net/http"
@@ -349,6 +350,87 @@ func TestJSON(t *testing.T) {
349350
assert.Equal(t, (*int64)(nil), recv.Z)
350351
}
351352

353+
func BenchmarkBindJSON(b *testing.B) {
354+
type Recv struct {
355+
X **struct {
356+
A []string `api:"body:'a'"`
357+
B int32
358+
C *[]uint16
359+
D *float32 `api:"body:'d'"`
360+
}
361+
Y string `api:"body:'y'"`
362+
}
363+
binder := binding.New("api")
364+
header := make(http.Header)
365+
header.Set("Content-Type", "application/json")
366+
test := func() {
367+
bodyReader := strings.NewReader(`{
368+
"X": {
369+
"a": ["a1","a2"],
370+
"B": 21,
371+
"C": [31,32],
372+
"d": 41
373+
},
374+
"y": "y1"
375+
}`)
376+
req := newRequest("", header, nil, bodyReader)
377+
recv := new(Recv)
378+
err := binder.Bind(recv, req, nil)
379+
if err != nil {
380+
b.Fatal(err)
381+
}
382+
}
383+
test()
384+
385+
b.ReportAllocs()
386+
b.ResetTimer()
387+
388+
for i := 0; i < b.N; i++ {
389+
test()
390+
}
391+
}
392+
393+
func BenchmarkStdJSON(b *testing.B) {
394+
type Recv struct {
395+
X **struct {
396+
A []string `json:"a"`
397+
B int32
398+
C *[]uint16
399+
D *float32 `json:"d"`
400+
}
401+
Y string `json:"y"`
402+
}
403+
header := make(http.Header)
404+
header.Set("Content-Type", "application/json")
405+
406+
b.ReportAllocs()
407+
b.ResetTimer()
408+
409+
for i := 0; i < b.N; i++ {
410+
bodyReader := strings.NewReader(`{
411+
"X": {
412+
"a": ["a1","a2"],
413+
"B": 21,
414+
"C": [31,32],
415+
"d": 41
416+
},
417+
"y": "y1"
418+
}`)
419+
420+
req := newRequest("", header, nil, bodyReader)
421+
recv := new(Recv)
422+
body, err := ioutil.ReadAll(req.Body)
423+
req.Body.Close()
424+
if err != nil {
425+
b.Fatal(err)
426+
}
427+
err = json.Unmarshal(body, recv)
428+
if err != nil {
429+
b.Fatal(err)
430+
}
431+
}
432+
}
433+
352434
type testPathParams struct{}
353435

354436
func (testPathParams) Get(name string) (string, bool) {

binding/jsonparam/gjson.go

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,23 @@ import (
2828
"strings"
2929
"sync"
3030

31+
"github.com/henrylee2cn/goutil/tpack"
3132
"github.com/tidwall/gjson"
3233
)
3334

3435
var fieldsmu sync.RWMutex
35-
var fields = make(map[string]map[string]int)
36+
var fields = make(map[int32]map[string]int)
37+
38+
func init() {
39+
gjson.DisableModifiers = true
40+
}
3641

3742
// Assign unmarshal
3843
func Assign(jsval gjson.Result, goval reflect.Value) {
3944
if jsval.Type == gjson.Null {
4045
return
4146
}
47+
t := goval.Type()
4248
switch goval.Kind() {
4349
default:
4450
case reflect.Ptr:
@@ -47,19 +53,21 @@ func Assign(jsval gjson.Result, goval reflect.Value) {
4753
Assign(jsval, newval.Elem())
4854
goval.Elem().Set(newval.Elem())
4955
} else {
50-
newval := reflect.New(goval.Type().Elem())
56+
newval := reflect.New(t.Elem())
5157
Assign(jsval, newval.Elem())
5258
goval.Set(newval)
5359
}
5460
case reflect.Struct:
61+
runtimeTypeID := tpack.From(goval).RuntimeTypeID()
5562
fieldsmu.RLock()
56-
sf := fields[goval.Type().String()]
63+
sf := fields[runtimeTypeID]
5764
fieldsmu.RUnlock()
5865
if sf == nil {
5966
fieldsmu.Lock()
6067
sf = make(map[string]int)
61-
for i := 0; i < goval.Type().NumField(); i++ {
62-
f := goval.Type().Field(i)
68+
numField := t.NumField()
69+
for i := 0; i < numField; i++ {
70+
f := t.Field(i)
6371
tag := strings.Split(f.Tag.Get("json"), ",")[0]
6472
if tag != "-" {
6573
if tag != "" {
@@ -70,7 +78,7 @@ func Assign(jsval gjson.Result, goval reflect.Value) {
7078
}
7179
}
7280
}
73-
fields[goval.Type().String()] = sf
81+
fields[runtimeTypeID] = sf
7482
fieldsmu.Unlock()
7583
}
7684
jsval.ForEach(func(key, value gjson.Result) bool {
@@ -83,12 +91,12 @@ func Assign(jsval gjson.Result, goval reflect.Value) {
8391
return true
8492
})
8593
case reflect.Slice:
86-
if goval.Type().Elem().Kind() == reflect.Uint8 && jsval.Type == gjson.String {
94+
if t.Elem().Kind() == reflect.Uint8 && jsval.Type == gjson.String {
8795
data, _ := base64.StdEncoding.DecodeString(jsval.String())
8896
goval.Set(reflect.ValueOf(data))
8997
} else {
9098
jsvals := jsval.Array()
91-
slice := reflect.MakeSlice(goval.Type(), len(jsvals), len(jsvals))
99+
slice := reflect.MakeSlice(t, len(jsvals), len(jsvals))
92100
for i := 0; i < len(jsvals); i++ {
93101
Assign(jsvals[i], slice.Index(i))
94102
}
@@ -105,7 +113,7 @@ func Assign(jsval gjson.Result, goval reflect.Value) {
105113
return true
106114
})
107115
case reflect.Map:
108-
if goval.Type().Key().Kind() == reflect.String && goval.Type().Elem().Kind() == reflect.Interface {
116+
if t.Key().Kind() == reflect.String && t.Elem().Kind() == reflect.Interface {
109117
goval.Set(reflect.ValueOf(jsval.Value()))
110118
}
111119
case reflect.Interface:
@@ -121,7 +129,7 @@ func Assign(jsval gjson.Result, goval reflect.Value) {
121129
case reflect.String:
122130
goval.SetString(jsval.String())
123131
}
124-
if len(goval.Type().PkgPath()) > 0 {
132+
if len(t.PkgPath()) > 0 {
125133
v := goval.Addr()
126134
if v.Type().NumMethod() > 0 {
127135
if u, ok := v.Interface().(json.Unmarshaler); ok {

binding/param_info.go

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -101,19 +101,8 @@ func (p *paramInfo) bindCookie(expr *tagexpr.TagExpr, cookies []*http.Cookie) er
101101
return p.bindStringSlice(expr, r)
102102
}
103103

104-
func (p *paramInfo) bindBody(expr *tagexpr.TagExpr, bodyCodec uint8, postForm url.Values, bodyBytes []byte) (bool, error) {
105-
switch bodyCodec {
106-
case formBody:
107-
return p.bindMapStrings(expr, postForm)
108-
case jsonBody:
109-
return p.bindJSON(expr, bodyBytes)
110-
}
111-
return false, p.contentTypeError
112-
}
113-
114-
func (p *paramInfo) bindJSON(expr *tagexpr.TagExpr, bodyBytes []byte) (bool, error) {
115-
r := gjson.Parse(goutil.BytesToString(bodyBytes))
116-
r = r.Get(p.namePath)
104+
func (p *paramInfo) bindJSON(expr *tagexpr.TagExpr, bodyString string) (bool, error) {
105+
r := gjson.Get(bodyString, p.namePath)
117106
if !r.Exists() {
118107
if p.required {
119108
return false, p.requiredError

binding/receiver.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"strings"
77

88
"github.com/bytedance/go-tagexpr"
9+
"github.com/henrylee2cn/goutil"
910
)
1011

1112
const (
@@ -69,11 +70,15 @@ func (r *receiver) getBodyCodec(req *http.Request) uint8 {
6970
}
7071
}
7172

72-
func (r *receiver) getBodyBytes(req *http.Request, must bool) ([]byte, error) {
73+
func (r *receiver) getBody(req *http.Request, must bool) ([]byte, string, error) {
7374
if must || r.hasRawBody {
74-
return copyBody(req)
75+
bodyBytes, err := copyBody(req)
76+
if err == nil {
77+
return bodyBytes, goutil.BytesToString(bodyBytes), nil
78+
}
79+
return bodyBytes, "", nil
7580
}
76-
return nil, nil
81+
return nil, "", nil
7782
}
7883

7984
const (

0 commit comments

Comments
 (0)