funcreadConfig() *Config { // read from xxx.json,省略 config := Config{} typ := reflect.TypeOf(config) value := reflect.Indirect(reflect.ValueOf(&config)) for i := 0; i < typ.NumField(); i++ { f := typ.Field(i) if v, ok := f.Tag.Lookup("json"); ok { key := fmt.Sprintf("CONFIG_%s", strings.ReplaceAll(strings.ToUpper(v), "-", "_")) if env, exist := os.LookupEnv(key); exist { value.FieldByName(f.Name).Set(reflect.ValueOf(env)) } } } return &config }
funcmain() { os.Setenv("CONFIG_SERVER_NAME", "global_server") os.Setenv("CONFIG_SERVER_IP", "10.0.0.1") os.Setenv("CONFIG_SERVER_URL", "geektutu.com") c := readConfig() fmt.Printf("%+v", c) }
funcBenchmarkSet(b *testing.B) { config := new(Config) b.ResetTimer() for i := 0; i < b.N; i++ { config.Name = "name" config.IP = "ip" config.URL = "url" config.Timeout = "timeout" } }
funcBenchmarkReflect_FieldSet(b *testing.B) { typ := reflect.TypeOf(Config{}) ins := reflect.New(typ).Elem() b.ResetTimer() for i := 0; i < b.N; i++ { ins.Field(0).SetString("name") ins.Field(1).SetString("ip") ins.Field(2).SetString("url") ins.Field(3).SetString("timeout") } }
funcBenchmarkReflect_FieldByNameSet(b *testing.B) { typ := reflect.TypeOf(Config{}) ins := reflect.New(typ).Elem() b.ResetTimer() for i := 0; i < b.N; i++ { ins.FieldByName("Name").SetString("name") ins.FieldByName("IP").SetString("ip") ins.FieldByName("URL").SetString("url") ins.FieldByName("Timeout").SetString("timeout") } }
测试结果如下:
1 2 3 4 5 6 7 8 9
$ go test -bench="Set$" . goos: darwin goarch: amd64 pkg: example/hpg-reflect BenchmarkSet-8 1000000000 0.302 ns/op BenchmarkReflect_FieldSet-8 33913672 34.5 ns/op BenchmarkReflect_FieldByNameSet-8 3775234 316 ns/op PASS ok example/hpg-reflect 3.066s
FieldByName 和 Field 十倍的性能差距让我对 FieldByName 的内部实现比较好奇,打开源代码一探究竟:
reflect/value.go
1 2 3 4 5 6 7 8 9 10
// FieldByName returns the struct field with the given name. // It returns the zero Value if no field was found. // It panics if v's Kind is not struct. func(v Value)FieldByName(name string)Value { v.mustBe(Struct) if f, ok := v.typ.FieldByName(name); ok { return v.FieldByIndex(f.Index) } return Value{} }
// FieldByName returns the struct field with the given name // and a boolean to indicate if the field was found. func(t *structType)FieldByName(name string)(f StructField, present bool) { // Quick check for top-level name, or struct without embedded fields. hasEmbeds := false if name != "" { for i := range t.fields { tf := &t.fields[i] if tf.name.name() == name { return t.Field(i), true } if tf.embedded() { hasEmbeds = true } } } if !hasEmbeds { return } return t.FieldByNameFunc(func(s string)bool { return s == name }) }
在上面的例子中可以看到,FieldByName 相比于 Field 有一个数量级的性能劣化。那在实际的应用中,就要避免直接调用 FieldByName。我们可以利用字典将 Name 和 Index 的映射缓存起来。避免每次反复查找,耗费大量的时间。
我们利用缓存,优化下刚才的测试用例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
funcBenchmarkReflect_FieldByNameCacheSet(b *testing.B) { typ := reflect.TypeOf(Config{}) cache := make(map[string]int) for i := 0; i < typ.NumField(); i++ { cache[typ.Field(i).Name] = i } ins := reflect.New(typ).Elem() b.ResetTimer() for i := 0; i < b.N; i++ { ins.Field(cache["Name"]).SetString("name") ins.Field(cache["IP"]).SetString("ip") ins.Field(cache["URL"]).SetString("url") ins.Field(cache["Timeout"]).SetString("timeout") } }
测试结果如下:
1 2 3 4 5 6 7 8 9 10
$ go test -bench="Set$" . -v goos: darwin goarch: amd64 pkg: example/hpg-reflect BenchmarkSet-8 1000000000 0.303 ns/op BenchmarkReflect_FieldSet-8 33429990 34.1 ns/op BenchmarkReflect_FieldByNameSet-8 3612130 331 ns/op BenchmarkReflect_FieldByNameCacheSet-8 14575906 78.2 ns/op PASS ok example/hpg-reflect 4.280s