我可以优化这个 Go 反射函数,让它不那么慢吗?

我正在开展一个项目,我需要为所有将受支持的方法构建部分更新。每个部分更新都需要一个不同的结构,具有不同的字段和字段数量,并且不知道哪些将存在或不存在。我决定遍历每个结构字段,如果存在,则将其添加到数组中以在最后返回。我还花了一些时间对几个似乎最现实的函数进行基准测试来解决这个问题,并帮助做出决定。

所有的结构字段都是指针。考虑到这一点,这些就是我编写的函数。

注意:我无法为此创建一个 playground 示例,因为它不支持基准测试。我将链接完整的课程,并将解释放在上面。

  1. 为每个部分更新结构创建一个映射函数,我将在其中分别检查每个字段。如果该字段不是nil,我会将值放入以 [key,value] 格式存储的二维数组中。处理完结构后,返回数组。

  2. 创建一个使用泛型和反射来执行与上述相同的映射函数。


慕婉清6462132
浏览 72回答 1
1回答

慕慕森

// main.gopackage mainimport (&nbsp; &nbsp; "reflect"&nbsp; &nbsp; "strings"&nbsp; &nbsp; "time")type updateRequest struct {&nbsp; &nbsp; FieldOne&nbsp; &nbsp;*string&nbsp; &nbsp; `json:"field_one,omitempty"`&nbsp; &nbsp; FieldTwo&nbsp; &nbsp;*string&nbsp; &nbsp; `json:"field_two,omitempty"`&nbsp; &nbsp; FieldThree *string&nbsp; &nbsp; `json:"field_three,omitempty"`&nbsp; &nbsp; FieldFour&nbsp; *string&nbsp; &nbsp; `json:"field_four,omitempty"`&nbsp; &nbsp; FieldFive&nbsp; *string&nbsp; &nbsp; `json:"field_five,omitempty"`&nbsp; &nbsp; FieldSix&nbsp; &nbsp;*time.Time `json:"field_six,omitempty" time_format:"2006-01-02"`}// Mapping function that would need to be recreated for each partial update struct.func ManualUpdateRequestMapping(req *updateRequest) [][]string {&nbsp; &nbsp; vals := make([][]string, 0, 6)&nbsp; &nbsp; if req.FieldOne != nil {&nbsp; &nbsp; &nbsp; &nbsp; vals = append(vals, []string{"field_one", *req.FieldOne})&nbsp; &nbsp; }&nbsp; &nbsp; if req.FieldTwo != nil && req.FieldThree != nil {&nbsp; &nbsp; &nbsp; &nbsp; vals = append(vals, []string{"field_two", *req.FieldTwo}, []string{"field_three", *req.FieldThree})&nbsp; &nbsp; }&nbsp; &nbsp; if req.FieldFour != nil {&nbsp; &nbsp; &nbsp; &nbsp; vals = append(vals, []string{"field_four", *req.FieldFour})&nbsp; &nbsp; }&nbsp; &nbsp; if req.FieldFive != nil {&nbsp; &nbsp; &nbsp; &nbsp; vals = append(vals, []string{"field_five", *req.FieldFive})&nbsp; &nbsp; }&nbsp; &nbsp; if req.FieldSix != nil {&nbsp; &nbsp; &nbsp; &nbsp; vals = append(vals, []string{"field_six", req.FieldSix.Format(time.RFC3339)})&nbsp; &nbsp; }&nbsp; &nbsp; return vals}// Generics and Reflection functionfunc ReflectUpdateRequestMapping[T *updateRequest](str T) [][]string {&nbsp; &nbsp; valHolder := reflect.ValueOf(*str)&nbsp; &nbsp; if valHolder.Kind() != reflect.Struct {&nbsp; &nbsp; &nbsp; &nbsp; return nil&nbsp; &nbsp; }&nbsp; &nbsp; vals := make([][]string, 0, valHolder.NumField())&nbsp; &nbsp; for i := 0; i < valHolder.NumField(); i++ {&nbsp; &nbsp; &nbsp; &nbsp; if valHolder.Field(i).IsNil() {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; continue&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; spl := strings.Split(valHolder.Type().Field(i).Tag.Get("json"), ",")&nbsp; &nbsp; &nbsp; &nbsp; if valHolder.Field(i).Elem().Type() != reflect.TypeOf(time.Time{}) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; vals = append(vals, []string{spl[0], valHolder.Field(i).Elem().String()})&nbsp; &nbsp; &nbsp; &nbsp; } else {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; vals = append(vals, []string{spl[0], valHolder.Field(i).Interface().(*time.Time).Format(time.RFC3339)})&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }&nbsp; &nbsp; return vals}这是我运行的基准方法:// main_test.gopackage mainimport (&nbsp; &nbsp; "testing"&nbsp; &nbsp; "time")func BenchmarkBoth(b *testing.B) {&nbsp; &nbsp; field1 := "testfield1"&nbsp; &nbsp; field2 := "testfield2"&nbsp; &nbsp; field3 := "testfield3"&nbsp; &nbsp; field4 := "testfield4"&nbsp; &nbsp; field5 := "testfield5"&nbsp; &nbsp; date1, _ := time.Parse(time.RFC3339, "2004-10-16T12:40:53.00Z")&nbsp; &nbsp; str := &updateRequest{&nbsp; &nbsp; &nbsp; &nbsp; FieldOne:&nbsp; &nbsp;&field1,&nbsp; &nbsp; &nbsp; &nbsp; FieldTwo:&nbsp; &nbsp;&field2,&nbsp; &nbsp; &nbsp; &nbsp; FieldThree: &field3,&nbsp; &nbsp; &nbsp; &nbsp; FieldFour:&nbsp; &field4,&nbsp; &nbsp; &nbsp; &nbsp; FieldFive:&nbsp; &field5,&nbsp; &nbsp; &nbsp; &nbsp; FieldSix:&nbsp; &nbsp;&date1,&nbsp; &nbsp; }&nbsp; &nbsp; b.Run("ManualUpdateRequestMapping", func(b *testing.B) {&nbsp; &nbsp; &nbsp; &nbsp; for i := 0; i < b.N; i++ {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _ = ManualUpdateRequestMapping(str)&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; })&nbsp; &nbsp; b.Run("ReflectUpdateRequestMapping", func(b *testing.B) {&nbsp; &nbsp; &nbsp; &nbsp; for i := 0; i < b.N; i++ {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _ = ReflectUpdateRequestMapping(str)&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; })}以下是使用的 CPU 和测试结果:cpu: 12th Gen Intel(R) Core(TM) i9-12900KFBenchmarkBoth/ManualUpdateRequestMapping-24&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 3560083&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;331.9 ns/op&nbsp; &nbsp; &nbsp; &nbsp;368 B/op&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 8 allocs/opBenchmarkBoth/ReflectUpdateRequestMapping-24&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;1393377&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;866.7 ns/op&nbsp; &nbsp; &nbsp; &nbsp;648 B/op&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;21 allocs/opPASSok&nbsp; &nbsp; &nbsp; com.example.stack&nbsp; &nbsp;3.745s我预计反射功能会更慢,但不会慢 ~2.5 倍。它还似乎在每次迭代中分配了大约 2.5 倍的资源。是我在上面的代码中搞砸了什么,还是反射慢了那么多?如果有任何建议可以使上面的代码更高效,我愿意接受所有建议。我已经使用 Go 工作了大约 3 个月,所以如果我在上面的代码中犯了任何 Golang 叛国罪,请宽容我。:)
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go