> ## Content Index
> Fetch the complete content index at: https://huizhou92.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Golang High-Performance Programming EP4: reflect
- URL: https://huizhou92.com/golang-high-performance-programming-ep4-reflect-2/
- Published: 2024-07-12T13:14:59.000Z
- Updated: 2026-09-08T02:31:06.000Z
- Description: Golang High-Performance Programming EP4: reflect. reflect allows Go to dynamically obtain the types and values of objects at runtime and create objects dyn。
- Author: huizhou92
- Tags: #Migrated-1788833207488, #Import 2026-09-08 02:07

[reflect](https://pkg.go.dev/reflect?ref=huizhou92.com) allows Go to dynamically obtain the types and values of objects at runtime and create objects dynamically. Reflection can help abstract and simplify code, enhancing development efficiency. The Go standard library and many open-source projects utilize Go’s reflection capabilities, such as the `json` package for serialization and deserialization and ORM frameworks like `gorm/xorm`. This article aims to explore and reflect on how to improve its performance.

[Example Code](https://github.com/hxzhouh/blog-example/tree/main/go/go%5Freflect?ref=huizhou92.com)

### How to Simplify Code Using Reflection

Let’s implement a simple functionality using reflection to see how it can help us simplify the code. Suppose we have a configuration class `Config`, each field representing a configuration item. We need to read environment variables prefixed with CONFIG\_xxxx and initialize `Config` them accordingly.

*List 1: MySQL config*

```go
// https://github.com/go-sql-driver/mysql/blob/v1.8.1/dsn.go#L37   
 type Config struct {   
     User   string `json:"user"`    // Username   
     Passwd string `json:"passwd"`  // Password (requires User)   
     Net    string `json:"net"`     // Network (e.g. "tcp", "tcp6", "unix". default: "tcp")   
     Addr   string `json:"addr"`    // Address (default: "127.0.0.1:3306" for "tcp" and "/tmp/mysql.sock" for "unix")   
     DBName string `json:"db_name"` // Database name   
     //..... 
 }
```

### Footguns

It’s easy to write code like this:

*List 2: Initializing `Config` using a for-loop*

```go
func InitConfig() *Config {   
     cfg := &Config{}   
     keys := []string{"CONFIG_MYSQL_USER", "CONFIG_MYSQL_PASSWD", "CONFIG_MYSQL_NET", "CONFIG_MYSQL_ADDR", "CONFIG_MYSQL_DB_NAME"}   
     for _, key := range keys {   
        if env, exist := os.LookupEnv(key); exist {   
           switch key {   
           case "CONFIG_MYSQL_USER":   
              cfg.User = env   
           case "CONFIG_MYSQL_PASSWORD":   
              cfg.Passwd = env   
           case "CONFIG_MYSQL_NET":   
              cfg.Net = env   
           case "CONFIG_MYSQL_ADDR":   
              cfg.Addr = env   
           case "CONFIG_MYSQL_DB_NAME":   
              cfg.DBName = env   
           }   
        }   
     }   
     return cfg   
 }
```

However, using hardcoding means that if the `Config` structure changes, such as modifying the corresponding `json` This logic must also change when fields change, or a configuration item is deleted or added. A bigger problem is that it's very error-prone and hard to test.

If we switch to using reflection, the implementation code would look like this:

*List 3: Initializing `Config` using `reflect`*

```css
func InitConfig2() *Config {   
     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_MYSQL_%s", strings.ToUpper(v))   
           if env, exist := os.LookupEnv(key); exist {   
              value.FieldByName(f.Name).Set(reflect.ValueOf(env))   
           }   
        }   
     }   
     return &config   
 }
```

The implementation logic is quite simple:

- At runtime, use reflection to obtain the `Tag` property of each field in `Config` and concatenate the corresponding environment variable names.
- Check if the environment variable exists. If it does, assign its value to the field. This way, regardless of adding or deleting fields `Config`, the `InitConfig` the function doesn't need modification. Isn't it much more straightforward than using a for loop?

### Reflection Performance

We’ve often heard that the performance of reflection is poor. When comparing [JSON parsing libraries](https://huizhou92.com/content/files/2026/09/analyze-various-high-performance-json-parsing-libraries-in-go-4c699fc12cba.html), we also verified that the official `JSON Unmarshal` library performs relatively poorly because it needs to execute more instructions. So, how poor is the performance of reflection exactly? Let's perform a benchmark to find out.

*List 4: Benchmark test for New and Reflect Performance*

```go
func BenchmarkNew(b *testing.B) {   
     var config *Config   
     for i := 0; i < b.N; i++ {   
        config = new(Config)   
     }   
     _ = config   
 }   
    
 func BenchmarkReflectNew(b *testing.B) {   
     var config *Config   
     typ := reflect.TypeOf(Config{})   
     b.ResetTimer()   
     for i := 0; i < b.N; i++ {   
        config, _ = reflect.New(typ).Interface().(*Config)   
     }   
     _ = config   
 } 
 ---- 
 ➜  go_reflect git:(main) ✗ go test --bench .  
 goos: darwin 
 goarch: arm64 
 pkg: blog-example/go/go_reflect 
 BenchmarkNew-10                 47675076                25.40 ns/op 
 BenchmarkReflectNew-10          36163776                32.51 ns/op 
 PASS 
 ok      blog-example/go/go_reflect      3.895s
```

Their performance gap isn't significant if it’s just a creation scenario. Let’s test the field modification scenario. Two ways to modify fields using reflection are *FieldByName* and *Field index mode*. We'll test the performance of both modes separately.

*List 5: Testing the performance of modifying fields*

```go
func BenchmarkFieldSet(b *testing.B) {   
     config := new(Config)   
     b.ResetTimer()   
     for i := 0; i < b.N; i++ {   
        config.Net = "tcp4"   
        config.Addr = "127.0.0.1:3306"   
        config.Passwd = "123456"   
        config.User = "admin"   
     }   
 }   
    
 func BenchmarkFieldSetFieldByName(b *testing.B) {   
     config := new(Config)   
     value := reflect.ValueOf(config).Elem()   
     b.ResetTimer()   
     for i := 0; i < b.N; i++ {   
        value.FieldByName("Net").SetString("tcp4")   
        value.FieldByName("Addr").SetString("127.0.0.1:3306")   
        value.FieldByName("Passwd").SetString("123456")   
        value.FieldByName("User").SetString("admin")   
     }   
 }   
 func BenchmarkFieldSetField(b *testing.B) {   
     config := new(Config)   
     value := reflect.Indirect(reflect.ValueOf(config))   
     b.ResetTimer()   
     for i := 0; i < b.N; i++ {   
        value.Field(0).SetString("tcp4")   
        value.Field(1).SetString("127.0.0.1:3306")   
        value.Field(2).SetString("123456")   
        value.Field(3).SetString("admin")   
     }   
 } 
 ---- 
 ➜  go_reflect git:(main) ✗ go test --bench="BenchmarkFieldSet*"           
 goos: darwin 
 goarch: arm64 
 pkg: blog-example/go/go_reflect 
 BenchmarkFieldSet-10                    1000000000               0.3282 ns/op 
 BenchmarkFieldSetFieldByName-10          6471114               185.3 ns/op 
 BenchmarkFieldSetField-10               100000000               11.88 ns/op 
 PASS 
 ok      blog-example/go/go_reflect      3.910s
```

The performance difference is significant. Non-reflective methods have a gap of two orders of magnitude compared to the reflect *Field index mode*. Compared to the reflect *FieldByName* mode, the gap reaches three orders of magnitude. One puzzling point is that the *FieldByName* mode and the *Field index mode* also have an order of magnitude difference. However, we can find the answer in the source code.

*List 6:* [*reflect/value.go*](https://go.googlesource.com/go/blob/82c371a307116450e9ab4dbce1853da3e69f4061/src/reflect/value.go?ref=huizhou92.com#L1345)

```go
func (v Value) FieldByName(name string) Value {   
     v.mustBe(Struct)   
     if f, ok := toRType(v.typ()).FieldByName(name); ok {   
        return v.FieldByIndex(f.Index)   
     }   
     return Value{}   
 } 
 ​ 
 // FieldByIndex returns the nested field corresponding to index.// It panics if evaluation requires stepping through a nil   
 // pointer or a field that is not a struct.   
 func (v Value) FieldByIndex(index []int) Value {   
     if len(index) == 1 {   
        return v.Field(index[0])   
     }   
     v.mustBe(Struct)   
     for i, x := range index {   
        if i > 0 {   
           if v.Kind() == Pointer && v.typ().Elem 
 ​ 
 ().Kind() == abi.Struct {   
              if v.IsNil() {   
                 panic("reflect: indirection through nil pointer to embedded struct")   
              }   
              v = v.Elem()   
           }   
        }   
        v = v.Field(x)   
     }   
     return v   
 }
```

*List 7:* [*reflect/type.go*](https://go.googlesource.com/go/blob/82c371a307116450e9ab4dbce1853da3e69f4061/src/reflect/type.go?ref=huizhou92.com#L760)

```go
func (t *rtype) FieldByName(name string) (StructField, bool) {   
     if t.Kind() != Struct {   
        panic("reflect: FieldByName of non-struct type " + t.String())   
     }   
     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 := 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 })   
 }
```

Internally, fields are stored sequentially, so accessing them by index has an O(1) efficiency, while accessing by `Name` requires traversing all fields, which has an O(N) efficiency. The more fields (including methods) a struct contains, the more significant the efficiency difference between the two methods. However, remembering the order of fields can be error-prone.

### How to Improve Performance

#### Avoid Using Reflect Whenever Possible

Using reflection for assignments is highly inefficient. If alternative methods exist, avoid using reflection, especially in repeatedly called hotspots. For example, in an RPC protocol, where structures need to be serialized and deserialized, avoid using Go’s built-in `json` `Marshal` and `Unmarshal` methods because the standard library's JSON serialization and deserialization are implemented using reflection. Using `fastjson` instead of the standard library can yield a performance improvement of about ten times.

#### Use `Field` Index Mode Whenever Possible

From the previous benchmark, the `Field` index mode is nearly an order of magnitude faster than the `FieldByName` method. This difference becomes more evident when there are more fields. However, using the `Field` index mode can be cumbersome because you need to remember the indexes, making it difficult to modify. In this case, you can use a map to cache the names and indexes.For example:

*List 8: Cache FieldByName and Field Index*

```go
func BenchmarkFieldSetFieldByNameCache(b *testing.B) {   
     config := new(Config)   
     typ := reflect.TypeOf(Config{})   
     value := reflect.ValueOf(config).Elem()   
     cache := make(map[string]int)   
     for i := 0; i < typ.NumField(); i++ {   
        cache[typ.Field(i).Name] = i   
     }   
     b.ResetTimer()   
     for i := 0; i < b.N; i++ {   
        value.Field(cache["Net"]).SetString("tcp4")   
        value.Field(cache["Addr"]).SetString("127.0.0.1:3306")   
        value.Field(cache["Passwd"]).SetString("123456")   
        value.Field(cache["User"]).SetString("admin")   
     }   
 } 
 ---- 
 BenchmarkFieldSetFieldByNameCache-10            32121740                36.85 ns/op
```

This method shows a fourfold improvement over direct use `FieldByName`, which is quite significant.

### Conclusion

1. This article does not delve into the details of `reflect`. If you want to learn more about `reflect`, you can refer to the following articles:
- [Learning to Use Go Reflection](https://huizhou92.com/content/files/2026/09/learning-to-use-go-reflection-822a0aed74b7.html) [Jon Bodner](https://huizhou92.com/content/files/2026/09/f6076075ccd0.html)
- [Go101: Reflection](https://go101.org/article/reflection.html?ref=huizhou92.com)
- [https://umakantv.com/blog/go-reflect](https://umakantv.com/blog/go-reflect?ref=huizhou92.com)

2\. Reflect is used extensively in various foundational libraries. Using reflect appropriately can simplify code, but it comes with performance costs, potentially reducing performance by up to three orders of magnitude in extreme cases.

3\. You can optimize performance using Field index + cache.

Do you have any other thoughts on reflection? Leave a comment and discuss it with me.

[文章索引](/article-index/)