Skip to content

Commit a600d97

Browse files
committed
Improve performance for "equalFieldType" function
OLD: ``` Benchmark_equalFieldType-12 3320424 361.0 ns/op 80 B/op 9 allocs/op ``` NEW: ``` Benchmark_equalFieldType-12 11102847 102.2 ns/op 16 B/op 3 allocs/op ``` + solve the problem with passing on the tag name
1 parent 34e45ec commit a600d97

File tree

8 files changed

+92
-62
lines changed

8 files changed

+92
-62
lines changed

binder/cookie.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func (b *CookieBinding) Bind(req *fasthttp.Request, out any) error {
2727

2828
k := utils.UnsafeString(key)
2929
v := utils.UnsafeString(val)
30-
err = formatBindData(out, data, k, v, b.EnableSplitting, false)
30+
err = formatBindData(b.Name(), out, data, k, v, b.EnableSplitting, false)
3131
})
3232

3333
if err != nil {

binder/form.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ func (b *FormBinding) Bind(req *fasthttp.Request, out any) error {
3636

3737
k := utils.UnsafeString(key)
3838
v := utils.UnsafeString(val)
39-
err = formatBindData(out, data, k, v, b.EnableSplitting, true)
39+
err = formatBindData(b.Name(), out, data, k, v, b.EnableSplitting, true)
4040
})
4141

4242
if err != nil {
@@ -55,15 +55,15 @@ func (b *FormBinding) bindMultipart(req *fasthttp.Request, out any) error {
5555

5656
data := make(map[string][]string)
5757
for key, values := range multipartForm.Value {
58-
err = formatBindData(out, data, key, values, b.EnableSplitting, true)
58+
err = formatBindData(b.Name(), out, data, key, values, b.EnableSplitting, true)
5959
if err != nil {
6060
return err
6161
}
6262
}
6363

6464
files := make(map[string][]*multipart.FileHeader)
6565
for key, values := range multipartForm.File {
66-
err = formatBindData(out, files, key, values, b.EnableSplitting, true)
66+
err = formatBindData(b.Name(), out, files, key, values, b.EnableSplitting, true)
6767
if err != nil {
6868
return err
6969
}

binder/header.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ func (b *HeaderBinding) Bind(req *fasthttp.Request, out any) error {
2626

2727
k := utils.UnsafeString(key)
2828
v := utils.UnsafeString(val)
29-
err = formatBindData(out, data, k, v, b.EnableSplitting, false)
29+
err = formatBindData(b.Name(), out, data, k, v, b.EnableSplitting, false)
3030
})
3131

3232
if err != nil {

binder/mapping.go

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -184,8 +184,8 @@ func isExported(f reflect.StructField) bool {
184184
return f.PkgPath == ""
185185
}
186186

187-
func fieldName(f reflect.StructField) string {
188-
name := f.Tag.Get("query")
187+
func fieldName(f reflect.StructField, aliasTag string) string {
188+
name := f.Tag.Get(aliasTag)
189189
if name == "" {
190190
name = f.Name
191191
} else {
@@ -200,9 +200,35 @@ type fieldInfo struct {
200200
nestedKinds map[reflect.Kind]struct{}
201201
}
202202

203-
var fieldCache sync.Map // map[reflect.Type]fieldInfo
203+
var (
204+
headerFieldCache sync.Map
205+
respHeaderFieldCache sync.Map
206+
cookieFieldCache sync.Map
207+
queryFieldCache sync.Map
208+
formFieldCache sync.Map
209+
uriFieldCache sync.Map
210+
)
204211

205-
func buildFieldInfo(t reflect.Type) fieldInfo {
212+
func getFieldCache(aliasTag string) *sync.Map {
213+
switch aliasTag {
214+
case "header":
215+
return &headerFieldCache
216+
case "respHeader":
217+
return &respHeaderFieldCache
218+
case "cookie":
219+
return &cookieFieldCache
220+
case "form":
221+
return &formFieldCache
222+
case "uri":
223+
return &uriFieldCache
224+
case "query":
225+
return &queryFieldCache
226+
}
227+
228+
panic("unknown alias tag: " + aliasTag)
229+
}
230+
231+
func buildFieldInfo(t reflect.Type, aliasTag string) fieldInfo {
206232
info := fieldInfo{
207233
names: make(map[string]reflect.Kind),
208234
nestedKinds: make(map[reflect.Kind]struct{}),
@@ -213,7 +239,7 @@ func buildFieldInfo(t reflect.Type) fieldInfo {
213239
if !isExported(f) {
214240
continue
215241
}
216-
info.names[fieldName(f)] = f.Type.Kind()
242+
info.names[fieldName(f, aliasTag)] = f.Type.Kind()
217243

218244
if f.Type.Kind() == reflect.Struct {
219245
for j := 0; j < f.Type.NumField(); j++ {
@@ -229,7 +255,7 @@ func buildFieldInfo(t reflect.Type) fieldInfo {
229255
return info
230256
}
231257

232-
func equalFieldType(out any, kind reflect.Kind, key string) bool {
258+
func equalFieldType(out any, kind reflect.Kind, key, aliasTag string) bool {
233259
typ := reflect.TypeOf(out).Elem()
234260
key = utils.ToLower(key)
235261

@@ -241,13 +267,17 @@ func equalFieldType(out any, kind reflect.Kind, key string) bool {
241267
return false
242268
}
243269

244-
val, ok := fieldCache.Load(typ)
270+
cache := getFieldCache(aliasTag)
271+
val, ok := cache.Load(typ)
245272
if !ok {
246-
info := buildFieldInfo(typ)
247-
val, _ = fieldCache.LoadOrStore(typ, info)
273+
info := buildFieldInfo(typ, aliasTag)
274+
val, _ = cache.LoadOrStore(typ, info)
248275
}
249276

250-
info := val.(fieldInfo)
277+
info, ok := val.(fieldInfo)
278+
if !ok {
279+
return false
280+
}
251281

252282
if k, ok := info.names[key]; ok && k == kind {
253283
return true
@@ -269,7 +299,7 @@ func FilterFlags(content string) string {
269299
return content
270300
}
271301

272-
func formatBindData[T, K any](out any, data map[string][]T, key string, value K, enableSplitting, supportBracketNotation bool) error { //nolint:revive // it's okay
302+
func formatBindData[T, K any](aliasTag string, out any, data map[string][]T, key string, value K, enableSplitting, supportBracketNotation bool) error { //nolint:revive // it's okay
273303
var err error
274304
if supportBracketNotation && strings.Contains(key, "[") {
275305
key, err = parseParamSquareBrackets(key)
@@ -285,15 +315,15 @@ func formatBindData[T, K any](out any, data map[string][]T, key string, value K,
285315
return fmt.Errorf("unsupported value type: %T", value)
286316
}
287317

288-
assignBindData(out, dataMap, key, v, enableSplitting)
318+
assignBindData(aliasTag, out, dataMap, key, v, enableSplitting)
289319
case []string:
290320
dataMap, ok := any(data).(map[string][]string)
291321
if !ok {
292322
return fmt.Errorf("unsupported value type: %T", value)
293323
}
294324

295325
for _, val := range v {
296-
assignBindData(out, dataMap, key, val, enableSplitting)
326+
assignBindData(aliasTag, out, dataMap, key, val, enableSplitting)
297327
}
298328
case []*multipart.FileHeader:
299329
for _, val := range v {
@@ -310,8 +340,8 @@ func formatBindData[T, K any](out any, data map[string][]T, key string, value K,
310340
return err
311341
}
312342

313-
func assignBindData(out any, data map[string][]string, key, value string, enableSplitting bool) { //nolint:revive // it's okay
314-
if enableSplitting && strings.Contains(value, ",") && equalFieldType(out, reflect.Slice, key) {
343+
func assignBindData(aliasTag string, out any, data map[string][]string, key, value string, enableSplitting bool) { //nolint:revive // it's okay
344+
if enableSplitting && strings.Contains(value, ",") && equalFieldType(out, reflect.Slice, key, aliasTag) {
315345
values := strings.Split(value, ",")
316346
for i := 0; i < len(values); i++ {
317347
data[key] = append(data[key], values[i])

binder/mapping_test.go

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -13,25 +13,25 @@ func Test_EqualFieldType(t *testing.T) {
1313
t.Parallel()
1414

1515
var out int
16-
require.False(t, equalFieldType(&out, reflect.Int, "key"))
16+
require.False(t, equalFieldType(&out, reflect.Int, "key", "query"))
1717

1818
var dummy struct{ f string }
19-
require.False(t, equalFieldType(&dummy, reflect.String, "key"))
19+
require.False(t, equalFieldType(&dummy, reflect.String, "key", "query"))
2020

2121
var dummy2 struct{ f string }
22-
require.False(t, equalFieldType(&dummy2, reflect.String, "f"))
22+
require.False(t, equalFieldType(&dummy2, reflect.String, "f", "query"))
2323

2424
var user struct {
2525
Name string
2626
Address string `query:"address"`
2727
Age int `query:"AGE"`
2828
}
29-
require.True(t, equalFieldType(&user, reflect.String, "name"))
30-
require.True(t, equalFieldType(&user, reflect.String, "Name"))
31-
require.True(t, equalFieldType(&user, reflect.String, "address"))
32-
require.True(t, equalFieldType(&user, reflect.String, "Address"))
33-
require.True(t, equalFieldType(&user, reflect.Int, "AGE"))
34-
require.True(t, equalFieldType(&user, reflect.Int, "age"))
29+
require.True(t, equalFieldType(&user, reflect.String, "name", "query"))
30+
require.True(t, equalFieldType(&user, reflect.String, "Name", "query"))
31+
require.True(t, equalFieldType(&user, reflect.String, "address", "query"))
32+
require.True(t, equalFieldType(&user, reflect.String, "Address", "query"))
33+
require.True(t, equalFieldType(&user, reflect.Int, "AGE", "query"))
34+
require.True(t, equalFieldType(&user, reflect.Int, "age", "query"))
3535

3636
var user2 struct {
3737
User struct {
@@ -41,12 +41,12 @@ func Test_EqualFieldType(t *testing.T) {
4141
} `query:"user"`
4242
}
4343

44-
require.True(t, equalFieldType(&user2, reflect.String, "user.name"))
45-
require.True(t, equalFieldType(&user2, reflect.String, "user.Name"))
46-
require.True(t, equalFieldType(&user2, reflect.String, "user.address"))
47-
require.True(t, equalFieldType(&user2, reflect.String, "user.Address"))
48-
require.True(t, equalFieldType(&user2, reflect.Int, "user.AGE"))
49-
require.True(t, equalFieldType(&user2, reflect.Int, "user.age"))
44+
require.True(t, equalFieldType(&user2, reflect.String, "user.name", "query"))
45+
require.True(t, equalFieldType(&user2, reflect.String, "user.Name", "query"))
46+
require.True(t, equalFieldType(&user2, reflect.String, "user.address", "query"))
47+
require.True(t, equalFieldType(&user2, reflect.String, "user.Address", "query"))
48+
require.True(t, equalFieldType(&user2, reflect.Int, "user.AGE", "query"))
49+
require.True(t, equalFieldType(&user2, reflect.Int, "user.age", "query"))
5050
}
5151

5252
func Test_ParseParamSquareBrackets(t *testing.T) {
@@ -199,7 +199,7 @@ func TestFormatBindData(t *testing.T) {
199199

200200
out := struct{}{}
201201
data := make(map[string][]string)
202-
err := formatBindData(out, data, "name", "John", false, false)
202+
err := formatBindData("query", out, data, "name", "John", false, false)
203203
if err != nil {
204204
t.Fatalf("unexpected error: %v", err)
205205
}
@@ -213,7 +213,7 @@ func TestFormatBindData(t *testing.T) {
213213

214214
out := struct{}{}
215215
data := make(map[string][]string)
216-
err := formatBindData(out, data, "age", 30, false, false) // int is unsupported
216+
err := formatBindData("query", out, data, "age", 30, false, false) // int is unsupported
217217
if err == nil {
218218
t.Fatal("expected an error, got nil")
219219
}
@@ -224,7 +224,7 @@ func TestFormatBindData(t *testing.T) {
224224

225225
out := struct{}{}
226226
data := make(map[string][]string)
227-
err := formatBindData(out, data, "invalid[", "value", false, true) // malformed bracket notation
227+
err := formatBindData("query", out, data, "invalid[", "value", false, true) // malformed bracket notation
228228
if err == nil {
229229
t.Fatal("expected an error, got nil")
230230
}
@@ -239,7 +239,7 @@ func TestFormatBindData(t *testing.T) {
239239
{Filename: "file1.txt"},
240240
{Filename: "file2.txt"},
241241
}
242-
err := formatBindData(out, data, "files", files, false, false)
242+
err := formatBindData("query", out, data, "files", files, false, false)
243243
if err != nil {
244244
t.Fatalf("unexpected error: %v", err)
245245
}
@@ -253,7 +253,7 @@ func TestFormatBindData(t *testing.T) {
253253

254254
out := struct{}{}
255255
data := map[string][]int{} // Incorrect type to force a casting error
256-
err := formatBindData(out, data, "key", "value", false, false)
256+
err := formatBindData("query", out, data, "key", "value", false, false)
257257
require.Equal(t, "unsupported value type: string", err.Error())
258258
})
259259
}
@@ -268,7 +268,7 @@ func TestAssignBindData(t *testing.T) {
268268
Colors []string `query:"colors"`
269269
}{}
270270
data := make(map[string][]string)
271-
assignBindData(&out, data, "colors", "red,blue,green", true)
271+
assignBindData("query", &out, data, "colors", "red,blue,green", true)
272272
require.Len(t, data["colors"], 3)
273273
})
274274

@@ -277,7 +277,7 @@ func TestAssignBindData(t *testing.T) {
277277

278278
var out []string
279279
data := make(map[string][]string)
280-
assignBindData(out, data, "color", "red,blue", false)
280+
assignBindData("query", out, data, "color", "red,blue", false)
281281
require.Len(t, data["color"], 1)
282282
})
283283
}
@@ -308,7 +308,7 @@ func Test_formatBindData_ErrorCases(t *testing.T) {
308308

309309
out := struct{}{}
310310
data := make(map[string][]string)
311-
err := formatBindData(out, data, "age", 30, false, false) // int is unsupported
311+
err := formatBindData("query", out, data, "age", 30, false, false) // int is unsupported
312312
require.Error(t, err)
313313
require.EqualError(t, err, "unsupported value type: int")
314314
})
@@ -318,7 +318,7 @@ func Test_formatBindData_ErrorCases(t *testing.T) {
318318

319319
out := struct{}{}
320320
data := make(map[string][]string)
321-
err := formatBindData(out, data, "map", map[string]string{"key": "value"}, false, false) // map is unsupported
321+
err := formatBindData("query", out, data, "map", map[string]string{"key": "value"}, false, false) // map is unsupported
322322
require.Error(t, err)
323323
require.EqualError(t, err, "unsupported value type: map[string]string")
324324
})
@@ -328,7 +328,7 @@ func Test_formatBindData_ErrorCases(t *testing.T) {
328328

329329
out := struct{}{}
330330
data := make(map[string][]string)
331-
err := formatBindData(out, data, "invalid[", "value", false, true) // malformed bracket notation
331+
err := formatBindData("query", out, data, "invalid[", "value", false, true) // malformed bracket notation
332332
require.Error(t, err)
333333
require.EqualError(t, err, "unmatched brackets")
334334
})
@@ -338,7 +338,7 @@ func Test_formatBindData_ErrorCases(t *testing.T) {
338338

339339
out := struct{}{}
340340
data := make(map[string][]string)
341-
err := formatBindData(out, data, "names", 123, false, false) // invalid type for []string
341+
err := formatBindData("query", out, data, "names", 123, false, false) // invalid type for []string
342342
require.Error(t, err)
343343
require.EqualError(t, err, "unsupported value type: int")
344344
})
@@ -350,15 +350,15 @@ func Benchmark_equalFieldType(b *testing.B) {
350350
}
351351
type User struct {
352352
Name string `query:"name"`
353-
Age int `query:"age"`
354353
Nested Nested `query:"user"`
354+
Age int `query:"age"`
355355
}
356356
var user User
357357

358358
b.ReportAllocs()
359359
for i := 0; i < b.N; i++ {
360-
equalFieldType(&user, reflect.String, "name")
361-
equalFieldType(&user, reflect.Int, "age")
362-
equalFieldType(&user, reflect.String, "user.name")
360+
equalFieldType(&user, reflect.String, "name", "query")
361+
equalFieldType(&user, reflect.Int, "age", "query")
362+
equalFieldType(&user, reflect.String, "user.name", "query")
363363
}
364364
}

binder/query.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func (b *QueryBinding) Bind(reqCtx *fasthttp.Request, out any) error {
2727

2828
k := utils.UnsafeString(key)
2929
v := utils.UnsafeString(val)
30-
err = formatBindData(out, data, k, v, b.EnableSplitting, true)
30+
err = formatBindData(b.Name(), out, data, k, v, b.EnableSplitting, true)
3131
})
3232

3333
if err != nil {

binder/resp_header.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func (b *RespHeaderBinding) Bind(resp *fasthttp.Response, out any) error {
2727

2828
k := utils.UnsafeString(key)
2929
v := utils.UnsafeString(val)
30-
err = formatBindData(out, data, k, v, b.EnableSplitting, false)
30+
err = formatBindData(b.Name(), out, data, k, v, b.EnableSplitting, false)
3131
})
3232

3333
if err != nil {

0 commit comments

Comments
 (0)