GO代码优化技巧一:提前初始化

173 阅读3分钟

技巧一:提前初始化

很多时候,我们的代码逻辑上,并不真的需要在每次运行的时候,才获取或设置某些值。而是可以在初始化阶段就把需要的值给确定出来。

比如在webapi组件中,我们要做一个header绑定的动作:在每次api请求时,可以将httpHeader的信息绑定到struct中。

省去我们做r.HttpContext.Header.GetValue("xxx"),非常省事。

autoBindHeader.png

优化前:

type HandleMiddleware struct { }
func (receiver HandleMiddleware) Invoke(httpContext *context.HttpContext) {
    // doing...
   receiver.bindHeader(httpContext, controllerVal) // 自动绑定头部
   // doing...
}

// 绑定header
func (receiver HandleMiddleware) bindHeader(httpContext *context.HttpContext, controllerVal reflect.Value) {
    controllerElem := controllerVal.Elem()
    controllerType := controllerElem.Type()
    for i := 0; i < controllerElem.NumField(); i++ {
        // 找到需要绑定头部的标记
        if controllerType.Field(i).Tag.Get("webapi") == "header" {
            controllerHeaderVal := controllerElem.Field(i)
            controllerHeaderType := controllerHeaderVal.Type()
            // 遍历需要将header绑定的结构体
            for headerIndex := 0; headerIndex < controllerHeaderVal.NumField(); headerIndex++ {
                headerFieldVal := controllerHeaderVal.Field(headerIndex)
                headerFieldType := headerFieldVal.Type()
                headerName := controllerHeaderType.Field(headerIndex).Tag.Get("webapi")
                if headerName == "" {
                    headerName = controllerHeaderType.Field(headerIndex).Name
                }
                headerVal := httpContext.Header.GetValue(headerName)
                if headerVal == "" {
                    continue
                }
                headerValue := parse.ConvertValue(headerVal, headerFieldType)
                headerFieldVal.Set(headerValue)
            }
        }
    }
}

可以看出这是一个中间件,意味着每次Api请求时都会被执行。而bindHeader方法是用来查找在控制器中包含tag = webapi:"header"的字段,如:

type TaskController struct {
   controller.BaseController
   Client clientApp.DTO `webapi:"header"`
}

找到后,将对这个字段(Client)做自动绑定。

bindHeader方法会每次都遍历这个TaskController这个控制器下的字段,然后找到符合Tag规则的字段,然后再遍历这个结构的字段,然后做绑定操作。

仔细观察会发现,每次API执行都要for一遍这个控制器来找到Client标签。其实这种查找方式是完全可以在初始阶段就确定的,而没必要在每次执行api时都来查找一遍。

优化后:

// Register 自动注册控制器下的所有Action方法
func Register(area string, c IController) {
   cVal := reflect.ValueOf(c)
   cRealType := reflect.Indirect(cVal).Type()

   // 查找是否需要自动绑定header
   var autoBindHeaderName string
   for i := 0; i < cRealType.NumField(); i++ {
      // 找到需要绑定头部的标记
      controllerFieldType := cRealType.Field(i)
      if controllerFieldType.Tag.Get("webapi") == "header" {
         autoBindHeaderName = controllerFieldType.Name
      }
   }
}

先在注册控制器前,找到这个字段,这个时间损耗,只会在运行前执行一次。然后在自动绑定header时,先判断这个字段存不存在即可:


// 绑定header
func (receiver HandleMiddleware) bindHeader(httpContext *context.HttpContext, controllerVal reflect.Value) {
   // 没有设置绑定字段,则不需要绑定
   if httpContext.Route.AutoBindHeaderName == "" {
      return
   }

   controllerHeaderVal := controllerVal.Elem().FieldByName(httpContext.Route.AutoBindHeaderName)
   controllerHeaderType := controllerHeaderVal.Type()

   // 遍历需要将header绑定的结构体
   for headerIndex := 0; headerIndex < controllerHeaderVal.NumField(); headerIndex++ {
      headerFieldVal := controllerHeaderVal.Field(headerIndex)
      headerFieldType := headerFieldVal.Type()
      headerName := controllerHeaderType.Field(headerIndex).Tag.Get("webapi")
      if headerName == "" {
         headerName = controllerHeaderType.Field(headerIndex).Name
      }
      headerVal := httpContext.Header.GetValue(headerName)
      if headerVal == "" {
         continue
      }
      headerValue := parse.ConvertValue(headerVal, headerFieldType)
      headerFieldVal.Set(headerValue)
   }
}

如果存在,则直接通过FieldByName(httpContext.Route.AutoBindHeaderName)快速定位到这个field。

这样我们就少了一个for遍历。

最后

事实上,这里示例的优化,并不能真正提升多少性能。但做为编写框架提供给go使用者而言,哪怕是快0.01ms,也是很有必要的。使用者关心的是:

  • 功能足够的强大
  • 使用起来足够优雅
  • 没有BUG
  • 持续的维护
  • 性能要足够的快

这里主要是提供一种思路:我们在执行某些逻辑时,确实需要在每次执行时才调用吗?

常见的新手错误在for循环里,读写数据库、redis,事实上我们可以一次性读出来,在操作。

最后,希望能帮助到大家。