Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf(util/gconv): improve performance for struct converting #3412

Merged
merged 21 commits into from
Apr 23, 2024

Conversation

wln32
Copy link
Member

@wln32 wln32 commented Mar 21, 2024

截至目前最新提交的性能大幅度提升

Benchmark_Struct_my_Fields8_Basic_MapToStruct       
Benchmark_Struct_my_Fields8_Basic_MapToStruct-8          482611             11708 ns/op            2074 B/op         44 allocs/op

Benchmark_Struct_gf_Fields8_Basic_MapToStruct                                                                                     
Benchmark_Struct_gf_Fields8_Basic_MapToStruct-8           214789             27090 ns/op            7668 B/op        138 allocs/op

@gqcn
Copy link
Member

gqcn commented Mar 21, 2024

@wln732 The ci fails.

@gqcn
Copy link
Member

gqcn commented Mar 25, 2024

涉及到核心转换逻辑调整,需要花点时间review

@Issues-translate-bot
Copy link

Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿


When it comes to adjusting the core conversion logic, it will take some time to review.

@gqcn
Copy link
Member

gqcn commented Apr 9, 2024

@wln32 有冲突了,更新pr呢,我继续review。

@Issues-translate-bot
Copy link

Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿


@wln32 There is a conflict. I have updated the PR and I will continue the review.

@wln32
Copy link
Member Author

wln32 commented Apr 10, 2024

@wln32 有冲突了,更新pr呢,我继续review。

@gqcn 有一个测试跑不过了,#3429 这个pr中修改了
文件路径 util/gconv/gconv_z_unit_issue_test.go 第100行

type StructFromIssue1227 struct {
	Name string `json:"中文Key"`
}
tests := []struct {
	name   string
	origin interface{}
	want   string
}
for _, tt := range tests {
	p := StructFromIssue1227{}
	if err := gconv.Struct(tt.origin, &p); err != nil {
		t.Error(err)
	}		
	t.Assert(p.Name, tt.want)
}

// 之前的,可以匹配到中文KEY
{
	name:   "Case5",
	origin: g.Map{"中文KEY": "n1"},
	want:   "n1",
}
// 修改后的,匹配不到中文KEY
{
	name:   "Case5",
	origin: g.Map{"中文KEY": "n1"},
	want:   "",
}

按照文档的匹配规则来说,忽略下划线特殊符号大小写之类的,不是应该可以匹配到吗?

@gqcn
Copy link
Member

gqcn commented Apr 10, 2024

@wln32 有冲突了,更新pr呢,我继续review。

@gqcn 有一个测试跑不过了,#3429 这个pr中修改了 文件路径 util/gconv/gconv_z_unit_issue_test.go 第100行

type StructFromIssue1227 struct {
	Name string `json:"中文Key"`
}
tests := []struct {
	name   string
	origin interface{}
	want   string
}
for _, tt := range tests {
	p := StructFromIssue1227{}
	if err := gconv.Struct(tt.origin, &p); err != nil {
		t.Error(err)
	}		
	t.Assert(p.Name, tt.want)
}

// 之前的,可以匹配到中文KEY
{
	name:   "Case5",
	origin: g.Map{"中文KEY": "n1"},
	want:   "n1",
}
// 修改后的,匹配不到中文KEY
{
	name:   "Case5",
	origin: g.Map{"中文KEY": "n1"},
	want:   "",
}

按照文档的匹配规则来说,忽略下划线特殊符号大小写之类的,不是应该可以匹配到吗?

新版本调整了其中一个case的期望值,因为费性能且毫无意义,可以按照master分支的结果来。

@wln32 wln32 requested review from gqcn and hailaz April 11, 2024 09:59
@gqcn
Copy link
Member

gqcn commented Apr 11, 2024

@wln32 The CI failed.

@wln32
Copy link
Member Author

wln32 commented Apr 12, 2024

@gqcn 强哥 目前gconv有行为不一致和字段多次赋值的情况

1.目前gconv.Struct行为不一致情况

例如以下代码

type ObjectMeta struct {
	Name string `json:"name"`
}

type ConfigMap struct {
	ObjectMeta `json:"metadata" `
	TypeMeta   string `json:",inline"`

	Immutable bool `json:"immutable" `
}

jsonStringData := `{
	"name":      "test",   
	"TypeMeta":  "j",   
	"immutable": false
}
`
var cfg2 ConfigMap
// 从字符串转换到结构体
err = gconv.Struct(jsonStringData, &cfg2)
if err != nil {
	panic(err)
}
g.Dump(cfg2)
// 反序列化结果
{
    Name:      "",   
    TypeMeta:  "j",  
    Immutable: false,
}

//=========================================

如果data是一个map时结果和上面的不一样
data := g.Map{
	"name":      "test",
	"TypeMeta":  "j",
	"immutable": false,
}
var cfg3 ConfigMap

err = gconv.Struct(data, &cfg3)
if err != nil {
	panic(err)
}
g.Dump(cfg3)
// 结果
{
    Name:      "test",
    TypeMeta:  "j",
    Immutable: false,
}

如果将ObjectMeta的json tag去掉,则结果输出一致
都能正确的识别到name字段并赋值

造成这一差异的情况的原因是,当从字符串转换到结构体时
会调用标准库的json.(*Decoder).Decode来处理
标准库对于匿名的结构体字段,且带有json tag时,
会认为是有名字的,而不是匿名的
例如以上例子中,ObjectMeta带上json tag时

需要这样才能匹配到name
"metadata":{
	"name":"test"
},

2.目前gconv.Struct对匿名结构体字段多次赋值

这种情况发生在从map转换到strcut时

以这个结构体作为例子

type ObjectMeta struct {
	Name string `json:"name"`
}

type ConfigMap struct {
	ObjectMeta // `json:"metadata" `
	TypeMeta   string `json:",inline"`

	Immutable bool `json:"immutable" `
}

for-struct-field

1.开始循环

       ①识别到匿名字段ObjectMeta,并进入递归处理,会把name字段赋值,然后退出
       ②对于TypeMeta和Immutable字段会存储到attrToCheckNameMap变量中

tagmap

2.处理带有tag的结构体字段

gstructs.TagMapName这个方法会把所有字段都解析出来,包括匿名结构体的字段
结果如下
name Name
,inline TypeMeta
immutable Immutable

如果ObjectMeta这种的匿名结构体带有json tag时,这个字段也会被解析。
gstructs.TagMapName的解析结果会被存储到tagToAttrNameMap这个变量中

3.开始赋值

根据用户自定义的映射规则赋值,略过

根据用户定义的tag来赋值,就是根据tagToAttrNameMap来做赋值,
由于name字段,在最开始的递归处已经被赋值,
这里重复解析,导致又被赋值

根据字段名赋值,略过
模糊匹配赋值,略过


repeat

如果嵌套的结构体字段数量比较多的话,非常影响效率

@Issues-translate-bot
Copy link

Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿


@gqcn Brother Qiang currently has inconsistent behavior and multiple field assignments in gconv.

1. The current behavior of gconv.Struct is inconsistent

For example the following code

typeObjectMeta struct {
Name string `json:"name"`
}

type ConfigMap struct {
ObjectMeta `json:" metadata" `
TypeMeta string `json:",inline"`

Immutable bool `json:" immutable" `
}

jsonStringData := `{
"name": "test",
"TypeMeta": "j",
"immutable": false
}
`
var cfg2 ConfigMap
//Convert from string to structure
err = gconv.Struct(jsonStringData, &cfg2)
if err != nil {
panic(err)
}
g.Dump(cfg2)
//Deserialize results
{
    Name: "",
    TypeMeta: "j",
    Immutable: false,
}

//==========================================

If data is a map, the result is different from the above
data := g.Map{
"name": "test",
"TypeMeta": "j",
"immutable": false,
}
var cfg3 ConfigMap

err = gconv.Struct(data, &cfg3)
if err != nil {
panic(err)
}
g.Dump(cfg3)
// result
{
    Name: "test",
    TypeMeta: "j",
    Immutable: false,
}

If the json tag of ObjectMeta is removed, the result output will be consistent.
The name field can be correctly identified and assigned a value.

The reason for this discrepancy is that when converting from string to struct
Will call json(*Decoder).Decode of the standard library to process
For anonymous structure fields with json tags, the standard library
will be considered named, not anonymous
For example, in the above example, when ObjectMeta brings json tag

This is needed to match the name
"metadata":{
"name":"test"
},

2. Currently gconv.Struct assigns multiple values ​​to anonymous structure fields.

This happens when converting from map to strcut

Take this structure as an example

typeObjectMeta struct {
Name string `json:"name"`
}

type ConfigMap struct {
ObjectMeta // `json:" metadata" `
TypeMeta string `json:",inline"`

Immutable bool `json:" immutable" `
}

for-struct-field

1. Start looping

① Recognize the anonymous field ObjectMeta and enter recursive processing. It will assign a value to the name field and then exit.
②TypeMeta and Immutable fields will be stored in the attrToCheckNameMap variable

tagmap

2. Process structure fields with tags

The gstructs.TagMapName method will parse out all fields, including fields of anonymous structures.
The results are as follows
name Name
,inlineTypeMeta
immutable Immutable

If an anonymous structure such as ObjectMeta has a json tag, this field will also be parsed.
The parsing result of gstructs.TagMapName will be stored in the variable tagToAttrNameMap

3. Start assignment

Assign values ​​according to user-defined mapping rules, skip

Assigning values ​​based on user-defined tags means assigning values ​​based on tagToAttrNameMap.
Because the name field has been assigned a value at the first recursion,
Repeated analysis here results in another assignment

Assign values ​​based on field names, skip
Fuzzy matching assignment, skip


repeat

If the number of nested structure fields is relatively large, it will greatly affect the efficiency.

@gogf gogf deleted a comment from Issues-translate-bot Apr 19, 2024
@gqcn
Copy link
Member

gqcn commented Apr 22, 2024

@wln32 wln32#1

@gqcn gqcn changed the title Improved the implementation of gconv.doStruct to facilitate maintenance enhance: enhance performance of struct converting for package gconv Apr 22, 2024
@gqcn gqcn changed the title enhance: enhance performance of struct converting for package gconv enhance: improve performance of struct converting for package gconv Apr 22, 2024
@gqcn gqcn merged commit 1013df1 into gogf:master Apr 23, 2024
23 checks passed
@gqcn gqcn changed the title enhance: improve performance of struct converting for package gconv perf(util/gconv): improve performance for struct converting Jun 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Data mapping to struct is slow when the number of database queries is large.
3 participants