FieldByName 和 Field 十倍的性能差距让我对 FieldByName 的内部实现比较好奇,打开源代码一探究竟:
reflect/value.go
// 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) }returnValue{}}
reflect/type.go
func (t *rtype) FieldByName(name string) (StructField, bool) {if t.Kind() != Struct {panic("reflect: FieldByName of non-struct type") } tt := (*structType)(unsafe.Pointer(t))return tt.FieldByName(name)}// 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 :=falseif 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 的映射缓存起来。避免每次反复查找,耗费大量的时间。
我们利用缓存,优化下刚才的测试用例:
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") }}