likes
comments
collection
share

# Go高性能编程-追求性能的地方不要用到反射

作者站长头像
站长
· 阅读数 36

1、起因

阿森在写某个业务代码的时候,需要更新mongoDB的某个字段的数据,输入是(key string, value string),同时需要更新缓存的对应字段。假设是这么个结构

type UserConfig struct {
    UserId      int64  `json:"user_id"`
    ShowName    *int32 `json:"show_name"`
    ShowAge     *int32 `json:"show_age"`
    ShowAddress *int32 `json:"show_address"`
    ShowPhone   *int32 `json:"show_phone"`
    ShowEmail   *int32 `json:"show_email"`
    ShowId      *int32 `json:"show_id"`
}

最开始的时候这个字段不是很多,直接用switch case就解决了,但是随着后续字段的增多。写着也是比较繁琐的。作为一名有着一坤年经验的java boy。这肯定能用反射实现啊。

2、实现


func Benchmark_Reflect_Just_Set(b *testing.B) {
 var showValue int32 = 1
 show := &showValue
 userConfig := UserConfig{
    UserId:      1,
    ShowName:    nil,
    ShowAge:     show,
    ShowAddress: show,
    ShowPhone:   show,
    ShowEmail:   show,
    ShowId:      show,
 }
 b.ResetTimer()
 for i := 0; i < b.N; i++ {
    userConfig.ShowName = show
 }
}

func Benchmark_Reflect_User_Name_Set(b *testing.B) {
 var showValue int32 = 1
 show := &showValue
 userConfig := UserConfig{
    UserId:      1,
    ShowName:    nil,
    ShowAge:     show,
    ShowAddress: show,
    ShowPhone:   show,
    ShowEmail:   show,
    ShowId:      show,
 }
 configType := reflect.TypeOf(userConfig)
 configValue := reflect.ValueOf(&userConfig).Elem()
 b.ResetTimer()
 for j := 0; j < b.N; j++ {
    for i := 0; i < configType.NumField(); i++ {
       field := configType.Field(i)
       if valueStr, ok := field.Tag.Lookup("json"); ok {
          if valueStr == "show_name" {
             fieldValue := configValue.Field(i)
             fieldValue.Set(reflect.ValueOf(show))
          }
       }
    }
 }
}

func Benchmark_Reflect_User_Name_CacheIndex_Set(b *testing.B) {
 var showValue int32 = 1
 show := &showValue
 userConfig := UserConfig{
    UserId:      1,
    ShowName:    nil,
    ShowAge:     show,
    ShowAddress: show,
    ShowPhone:   show,
    ShowEmail:   show,
    ShowId:      show,
 }
 configType := reflect.TypeOf(userConfig)
 configValue := reflect.ValueOf(&userConfig).Elem()
 cacheIndex := -1
 b.ResetTimer()
 for j := 0; j < b.N; j++ {
    if cacheIndex != -1 {
       configValue.Field(cacheIndex).Set(reflect.ValueOf(show))
    } else {
       for i := 0; i < configType.NumField(); i++ {
          field := configType.Field(i)
          if valueStr, ok := field.Tag.Lookup("json"); ok {
             if valueStr == "show_name" {
                cacheIndex = i
                fieldValue := configValue.Field(i)
                fieldValue.Set(reflect.ValueOf(show))
             }
          }
       }
    }
 }
}

我简单跑写了个demo,尝试了一下

标题总数单次耗时
Reflect_Just_Set-1010000000000.3224 ns/op
Reflect_User_Name_Set-102734923439.4 ns/op
Reflect_User_Name_CacheIndex_Set-101467339917.238 ns/op

这里的3个方法分别是, 直接设置key的值, 通过遍历先去找到key,再去设置值, 还有第一遍历的时候缓存key的index位置,之后直接设置值。 以为直接设置为基准。反射走key的index缓存的耗时是他的20倍+,如果不缓存index,一个一个去遍历,耗时是直接设置的1300多倍。

3、总结

看起来是比较唬人的,但是有个核心关键是的1ms等于整整100w ns,但是因为我这个是更新请求,这个cost在整个链路占比基本可以忽略不计。所以用反射在业务代码里面还是能接受的。(偷懒)

但是倘若你是写某些高性能组件,反射优化后的cost,也仍然比原生的要慢20倍。这是无法接受的。

转载自:https://juejin.cn/post/7350868887322034176
评论
请登录