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
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 相比于 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