From e1dd5cce7d18992d510928390ff16b4463103b47 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 8 Nov 2020 00:06:05 +0800 Subject: [PATCH] improve performance for gconv.Struct --- internal/structs/structs_tag.go | 1 + internal/utils/utils_str.go | 24 ++++++++++--------- internal/utils/utils_z_bench_test.go | 29 ++++++++++++++++++++++ internal/utils/utils_z_test.go | 6 +++++ util/gconv/gconv_struct.go | 13 +++------- util/gconv/gconv_z_bench_struct_test.go | 32 +++++++++++++++++++++++++ 6 files changed, 84 insertions(+), 21 deletions(-) create mode 100644 internal/utils/utils_z_bench_test.go create mode 100644 util/gconv/gconv_z_bench_struct_test.go diff --git a/internal/structs/structs_tag.go b/internal/structs/structs_tag.go index 08e3ecb6b..a15c688ae 100644 --- a/internal/structs/structs_tag.go +++ b/internal/structs/structs_tag.go @@ -24,6 +24,7 @@ func TagFields(pointer interface{}, priority []string) []*Field { // doTagFields retrieves the tag and corresponding attribute name from . It also filters repeated // tag internally. // The parameter should be type of struct/*struct. +// TODO remove third-party package "structs" by reducing the reflect usage to improve the performance. func doTagFields(pointer interface{}, priority []string, tagMap map[string]struct{}) []*Field { // If points to an invalid address, for example a nil variable, // it here creates an empty struct using reflect feature. diff --git a/internal/utils/utils_str.go b/internal/utils/utils_str.go index b14e13afb..7b827fd5c 100644 --- a/internal/utils/utils_str.go +++ b/internal/utils/utils_str.go @@ -7,16 +7,10 @@ package utils import ( - "regexp" + "bytes" "strings" ) -var ( - // replaceCharReg is the regular expression object for replacing chars in key. - // It is used for function EqualFoldWithoutChars. - replaceCharReg, _ = regexp.Compile(`[\-\.\_\s]+`) -) - // IsLetterUpper checks whether the given byte b is in upper case. func IsLetterUpper(b byte) bool { if b >= byte('A') && b <= byte('Z') { @@ -83,11 +77,19 @@ func ReplaceByMap(origin string, replaces map[string]string) string { return origin } +// RemoveSymbols removes all symbols from string and lefts only numbers and letters. +func RemoveSymbols(s string) string { + buffer := bytes.NewBuffer(nil) + for _, c := range s { + if (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') { + buffer.WriteByte(byte(c)) + } + } + return buffer.String() +} + // EqualFoldWithoutChars checks string and equal case-insensitively, // with/without chars '-'/'_'/'.'/' '. func EqualFoldWithoutChars(s1, s2 string) bool { - return strings.EqualFold( - replaceCharReg.ReplaceAllString(s1, ""), - replaceCharReg.ReplaceAllString(s2, ""), - ) + return strings.EqualFold(RemoveSymbols(s1), RemoveSymbols(s2)) } diff --git a/internal/utils/utils_z_bench_test.go b/internal/utils/utils_z_bench_test.go new file mode 100644 index 000000000..13320b73a --- /dev/null +++ b/internal/utils/utils_z_bench_test.go @@ -0,0 +1,29 @@ +// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package utils_test + +import ( + "github.com/gogf/gf/internal/utils" + "regexp" + "testing" +) + +var ( + replaceCharReg, _ = regexp.Compile(`[\-\.\_\s]+`) +) + +func Benchmark_RemoveSymbols(b *testing.B) { + for i := 0; i < b.N; i++ { + utils.RemoveSymbols(`-a-b._a c1!@#$%^&*()_+:";'.,'01`) + } +} + +func Benchmark_RegularReplaceChars(b *testing.B) { + for i := 0; i < b.N; i++ { + replaceCharReg.ReplaceAllString(`-a-b._a c1!@#$%^&*()_+:";'.,'01`, "") + } +} diff --git a/internal/utils/utils_z_test.go b/internal/utils/utils_z_test.go index be66e0730..a26e86e7a 100644 --- a/internal/utils/utils_z_test.go +++ b/internal/utils/utils_z_test.go @@ -63,3 +63,9 @@ func Test_ReadCloser(t *testing.T) { t.Assert(r, []byte{1, 2, 3, 4}) }) } + +func Test_RemoveSymbols(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + t.Assert(utils.RemoveSymbols(`-a-b._a c1!@#$%^&*()_+:";'.,'01`), `abac101`) + }) +} diff --git a/util/gconv/gconv_struct.go b/util/gconv/gconv_struct.go index 0b639ef8a..bb5068807 100644 --- a/util/gconv/gconv_struct.go +++ b/util/gconv/gconv_struct.go @@ -12,19 +12,12 @@ import ( "github.com/gogf/gf/internal/empty" "github.com/gogf/gf/internal/json" "reflect" - "regexp" "strings" "github.com/gogf/gf/internal/structs" "github.com/gogf/gf/internal/utils" ) -var ( - // replaceCharReg is the regular expression object for replacing chars - // in map keys and attribute names. - replaceCharReg, _ = regexp.Compile(`[\-\.\_\s]+`) -) - // Struct maps the params key-value pairs to the corresponding struct object's attributes. // The third parameter is unnecessary, indicating the mapping rules between the // custom key name and the attribute name(case sensitive). @@ -193,7 +186,7 @@ func doStruct(params interface{}, pointer interface{}, mapping ...map[string]str } } else { tempName = elemFieldType.Name - attrMap[tempName] = replaceCharReg.ReplaceAllString(tempName, "") + attrMap[tempName] = utils.RemoveSymbols(tempName) } } if len(attrMap) == 0 { @@ -204,7 +197,7 @@ func doStruct(params interface{}, pointer interface{}, mapping ...map[string]str // and the value is its replaced tag name for later comparison to improve performance. tagMap := make(map[string]string) for k, v := range structs.TagMapName(pointer, StructTagPriority) { - tagMap[v] = replaceCharReg.ReplaceAllString(k, "") + tagMap[v] = utils.RemoveSymbols(k) } var ( @@ -213,7 +206,7 @@ func doStruct(params interface{}, pointer interface{}, mapping ...map[string]str ) for mapK, mapV := range paramsMap { attrName = "" - checkName = replaceCharReg.ReplaceAllString(mapK, "") + checkName = utils.RemoveSymbols(mapK) // Loop to find the matched attribute name with or without // string cases and chars like '-'/'_'/'.'/' '. diff --git a/util/gconv/gconv_z_bench_struct_test.go b/util/gconv/gconv_z_bench_struct_test.go new file mode 100644 index 000000000..459aa1abe --- /dev/null +++ b/util/gconv/gconv_z_bench_struct_test.go @@ -0,0 +1,32 @@ +// Copyright 2017-2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +// go test *.go -bench=".*" -benchmem + +package gconv + +import ( + "testing" +) + +type structType struct { + Name string + Score int +} + +var ( + structMap = map[string]interface{}{ + "name": "gf", + "score": 100, + } + structPointer = new(structType) +) + +func Benchmark_Struct_Basic(b *testing.B) { + for i := 0; i < b.N; i++ { + Struct(structMap, structPointer) + } +}