Skip to content

Commit

Permalink
chore(binding): Improve performance
Browse files Browse the repository at this point in the history
Change-Id: I0f5083700848a87eff617ae2a391070af83eddbc
  • Loading branch information
andeya committed Jun 10, 2019
1 parent b46ca7f commit 6265a5b
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 37 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ _testmain.go
*.cfg
*.pptx
*.log
*nohup.out
*.out
*.sublime-project
*.sublime-workspace
.DS_Store
Expand Down
38 changes: 28 additions & 10 deletions binding/bind.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ package binding
import (
"net/http"
"reflect"
"sync"
_ "unsafe"

"github.com/bytedance/go-tagexpr"
"github.com/bytedance/go-tagexpr/validator"
"github.com/henrylee2cn/goutil"
"github.com/henrylee2cn/goutil/tpack"
)

Expand All @@ -27,7 +27,8 @@ const (
type Binding struct {
level Level
vd *validator.Validator
recvs goutil.Map
recvs map[int32]*receiver
lock sync.RWMutex
bindErrFactory func(failField, msg string) error
}

Expand All @@ -40,7 +41,7 @@ func New(tagName string) *Binding {
}
b := &Binding{
vd: validator.New(tagName),
recvs: goutil.AtomicMap(),
recvs: make(map[int32]*receiver, 1024),
}
return b.SetLevel(FirstAndTagged).SetErrorFactory(nil, nil)
}
Expand Down Expand Up @@ -124,16 +125,18 @@ func (b *Binding) structValueOf(structPointer interface{}) (reflect.Value, error

func (b *Binding) getObjOrPrepare(value reflect.Value) (*receiver, error) {
runtimeTypeID := tpack.From(value).RuntimeTypeID()
i, ok := b.recvs.Load(runtimeTypeID)
b.lock.RLock()
recv, ok := b.recvs[runtimeTypeID]
b.lock.RUnlock()
if ok {
return i.(*receiver), nil
return recv, nil
}

expr, err := b.vd.VM().Run(reflect.New(value.Type()).Elem())
if err != nil {
return nil, err
}
var recv = &receiver{
recv = &receiver{
params: make([]*paramInfo, 0, 16),
}
var errExprSelector tagexpr.ExprSelector
Expand Down Expand Up @@ -239,7 +242,10 @@ func (b *Binding) getObjOrPrepare(value reflect.Value) (*receiver, error) {

recv.initParams()

b.recvs.Store(runtimeTypeID, recv)
b.lock.Lock()
b.recvs[runtimeTypeID] = recv
b.lock.Unlock()

return recv, nil
}

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

bodyCodec := recv.getBodyCodec(req)

bodyBytes, err := recv.getBodyBytes(req, bodyCodec == jsonBody)
bodyBytes, bodyString, err := recv.getBody(req, bodyCodec == jsonBody)
if err != nil {
return false, err
}
Expand All @@ -280,12 +286,24 @@ func (b *Binding) bind(value reflect.Value, req *http.Request, pathParams PathPa
case cookie:
err = param.bindCookie(expr, cookies)
case body:
_, err = param.bindBody(expr, bodyCodec, postForm, bodyBytes)
switch bodyCodec {
case formBody:
_, err = param.bindMapStrings(expr, postForm)
case jsonBody:
_, err = param.bindJSON(expr, bodyString)
default:
err = param.contentTypeError
}
case raw_body:
err = param.bindRawBody(expr, bodyBytes)
default:
var found bool
found, err = param.bindBody(expr, bodyCodec, postForm, bodyBytes)
switch bodyCodec {
case formBody:
found, err = param.bindMapStrings(expr, postForm)
case jsonBody:
found, err = param.bindJSON(expr, bodyString)
}
if !found {
_, err = param.bindQuery(expr, queryValues)
}
Expand Down
82 changes: 82 additions & 0 deletions binding/bind_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package binding_test

import (
"bytes"
"encoding/json"
"io"
"io/ioutil"
"net/http"
Expand Down Expand Up @@ -349,6 +350,87 @@ func TestJSON(t *testing.T) {
assert.Equal(t, (*int64)(nil), recv.Z)
}

func BenchmarkBindJSON(b *testing.B) {
type Recv struct {
X **struct {
A []string `api:"body:'a'"`
B int32
C *[]uint16
D *float32 `api:"body:'d'"`
}
Y string `api:"body:'y'"`
}
binder := binding.New("api")
header := make(http.Header)
header.Set("Content-Type", "application/json")
test := func() {
bodyReader := strings.NewReader(`{
"X": {
"a": ["a1","a2"],
"B": 21,
"C": [31,32],
"d": 41
},
"y": "y1"
}`)
req := newRequest("", header, nil, bodyReader)
recv := new(Recv)
err := binder.Bind(recv, req, nil)
if err != nil {
b.Fatal(err)
}
}
test()

b.ReportAllocs()
b.ResetTimer()

for i := 0; i < b.N; i++ {
test()
}
}

func BenchmarkStdJSON(b *testing.B) {
type Recv struct {
X **struct {
A []string `json:"a"`
B int32
C *[]uint16
D *float32 `json:"d"`
}
Y string `json:"y"`
}
header := make(http.Header)
header.Set("Content-Type", "application/json")

b.ReportAllocs()
b.ResetTimer()

for i := 0; i < b.N; i++ {
bodyReader := strings.NewReader(`{
"X": {
"a": ["a1","a2"],
"B": 21,
"C": [31,32],
"d": 41
},
"y": "y1"
}`)

req := newRequest("", header, nil, bodyReader)
recv := new(Recv)
body, err := ioutil.ReadAll(req.Body)
req.Body.Close()
if err != nil {
b.Fatal(err)
}
err = json.Unmarshal(body, recv)
if err != nil {
b.Fatal(err)
}
}
}

type testPathParams struct{}

func (testPathParams) Get(name string) (string, bool) {
Expand Down
28 changes: 18 additions & 10 deletions binding/jsonparam/gjson.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,23 @@ import (
"strings"
"sync"

"github.com/henrylee2cn/goutil/tpack"
"github.com/tidwall/gjson"
)

var fieldsmu sync.RWMutex
var fields = make(map[string]map[string]int)
var fields = make(map[int32]map[string]int)

func init() {
gjson.DisableModifiers = true
}

// Assign unmarshal
func Assign(jsval gjson.Result, goval reflect.Value) {
if jsval.Type == gjson.Null {
return
}
t := goval.Type()
switch goval.Kind() {
default:
case reflect.Ptr:
Expand All @@ -47,19 +53,21 @@ func Assign(jsval gjson.Result, goval reflect.Value) {
Assign(jsval, newval.Elem())
goval.Elem().Set(newval.Elem())
} else {
newval := reflect.New(goval.Type().Elem())
newval := reflect.New(t.Elem())
Assign(jsval, newval.Elem())
goval.Set(newval)
}
case reflect.Struct:
runtimeTypeID := tpack.From(goval).RuntimeTypeID()
fieldsmu.RLock()
sf := fields[goval.Type().String()]
sf := fields[runtimeTypeID]
fieldsmu.RUnlock()
if sf == nil {
fieldsmu.Lock()
sf = make(map[string]int)
for i := 0; i < goval.Type().NumField(); i++ {
f := goval.Type().Field(i)
numField := t.NumField()
for i := 0; i < numField; i++ {
f := t.Field(i)
tag := strings.Split(f.Tag.Get("json"), ",")[0]
if tag != "-" {
if tag != "" {
Expand All @@ -70,7 +78,7 @@ func Assign(jsval gjson.Result, goval reflect.Value) {
}
}
}
fields[goval.Type().String()] = sf
fields[runtimeTypeID] = sf
fieldsmu.Unlock()
}
jsval.ForEach(func(key, value gjson.Result) bool {
Expand All @@ -83,12 +91,12 @@ func Assign(jsval gjson.Result, goval reflect.Value) {
return true
})
case reflect.Slice:
if goval.Type().Elem().Kind() == reflect.Uint8 && jsval.Type == gjson.String {
if t.Elem().Kind() == reflect.Uint8 && jsval.Type == gjson.String {
data, _ := base64.StdEncoding.DecodeString(jsval.String())
goval.Set(reflect.ValueOf(data))
} else {
jsvals := jsval.Array()
slice := reflect.MakeSlice(goval.Type(), len(jsvals), len(jsvals))
slice := reflect.MakeSlice(t, len(jsvals), len(jsvals))
for i := 0; i < len(jsvals); i++ {
Assign(jsvals[i], slice.Index(i))
}
Expand All @@ -105,7 +113,7 @@ func Assign(jsval gjson.Result, goval reflect.Value) {
return true
})
case reflect.Map:
if goval.Type().Key().Kind() == reflect.String && goval.Type().Elem().Kind() == reflect.Interface {
if t.Key().Kind() == reflect.String && t.Elem().Kind() == reflect.Interface {
goval.Set(reflect.ValueOf(jsval.Value()))
}
case reflect.Interface:
Expand All @@ -121,7 +129,7 @@ func Assign(jsval gjson.Result, goval reflect.Value) {
case reflect.String:
goval.SetString(jsval.String())
}
if len(goval.Type().PkgPath()) > 0 {
if len(t.PkgPath()) > 0 {
v := goval.Addr()
if v.Type().NumMethod() > 0 {
if u, ok := v.Interface().(json.Unmarshaler); ok {
Expand Down
15 changes: 2 additions & 13 deletions binding/param_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,19 +101,8 @@ func (p *paramInfo) bindCookie(expr *tagexpr.TagExpr, cookies []*http.Cookie) er
return p.bindStringSlice(expr, r)
}

func (p *paramInfo) bindBody(expr *tagexpr.TagExpr, bodyCodec uint8, postForm url.Values, bodyBytes []byte) (bool, error) {
switch bodyCodec {
case formBody:
return p.bindMapStrings(expr, postForm)
case jsonBody:
return p.bindJSON(expr, bodyBytes)
}
return false, p.contentTypeError
}

func (p *paramInfo) bindJSON(expr *tagexpr.TagExpr, bodyBytes []byte) (bool, error) {
r := gjson.Parse(goutil.BytesToString(bodyBytes))
r = r.Get(p.namePath)
func (p *paramInfo) bindJSON(expr *tagexpr.TagExpr, bodyString string) (bool, error) {
r := gjson.Get(bodyString, p.namePath)
if !r.Exists() {
if p.required {
return false, p.requiredError
Expand Down
11 changes: 8 additions & 3 deletions binding/receiver.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"strings"

"github.com/bytedance/go-tagexpr"
"github.com/henrylee2cn/goutil"
)

const (
Expand Down Expand Up @@ -69,11 +70,15 @@ func (r *receiver) getBodyCodec(req *http.Request) uint8 {
}
}

func (r *receiver) getBodyBytes(req *http.Request, must bool) ([]byte, error) {
func (r *receiver) getBody(req *http.Request, must bool) ([]byte, string, error) {
if must || r.hasRawBody {
return copyBody(req)
bodyBytes, err := copyBody(req)
if err == nil {
return bodyBytes, goutil.BytesToString(bodyBytes), nil
}
return bodyBytes, "", nil
}
return nil, nil
return nil, "", nil
}

const (
Expand Down

0 comments on commit 6265a5b

Please sign in to comment.