DEV Community

Cover image for Golang Reflection: Is It Slow?
Leapcell
Leapcell

Posted on

Golang Reflection: Is It Slow?

Image description

Why is Reflection Needed?

First, we need to understand what benefits reflection can bring. If it doesn't bring any advantages, then in fact, we don't need to use it and don't need to worry about the impact on performance.

The Implementation Principle of Reflection in Go Language

The Go language has few syntax elements and a simple design, so it doesn't have particularly strong expressive power. However, the reflect package in the Go language can make up for some of its syntactic disadvantages. Reflection can reduce repetitive coding work, and toolkits use reflection to handle different struct input parameters.

Using Reflection to Judge Whether a Struct is Empty

Business Scenario

In this way, when the incoming struct is empty, we can return directly without concatenating SQL, thus avoiding full - table scans and slow SQL.

Implementation without Using Reflection

If we don't use reflection, when we need to determine whether a struct is empty, we need to check each field one by one. The implementation is as follows:

type aStruct struct {
    Name string
    Male string
}

func (s *aStruct) IsEmpty() bool {
    return s.Male == "" && s.Name == ""
}

type complexSt struct {
    A        aStruct
    S        []string
    IntValue int
}

func (c *complexSt) IsEmpty() bool {
    return c.A.IsEmpty() && len(c.S) == 0 && c.IntValue == 0
}
Enter fullscreen mode Exit fullscreen mode

At this time, if we need to add a new struct to judge whether it is empty, then we need to implement the corresponding method to check each field.

Implementation Using Reflection

If we use reflection to implement it, we can refer to: Golang Empty Struct Judgment. At this time, we only need to pass in the corresponding struct to get whether the corresponding data is empty, without the need for repeated implementation.

Performance Comparison

func BenchmarkReflectIsStructEmpty(b *testing.B) {
    s := complexSt{
        A:        aStruct{},
        S:        make([]string, 0),
        IntValue: 0,
    }
    for i := 0; i < b.N; i++ {
        IsStructEmpty(s)
    }
}

func BenchmarkNormalIsStructEmpty(b *testing.B) {
    s := complexSt{
        A:        aStruct{},
        S:        make([]string, 0),
        IntValue: 0,
    }
    for i := 0; i < b.N; i++ {
        s.IsEmpty()
    }
}
Enter fullscreen mode Exit fullscreen mode

Executing Performance Tests

# -benchmem to view the number of memory allocations per operation
# -benchtime=3s to specify the execution time as 3s. Generally, the results obtained in 1s, 3s, and 5s are similar. If the performance is poor, the longer the execution time, the more accurate the average performance value.
# -count=3 to specify the number of executions. Multiple executions can ensure accuracy.
# -cpu n to specify the number of CPU cores. Generally, increasing the number of CPU cores will improve performance, but it is not a positive - correlation relationship. Because context switching will have an impact when there are more cores, it depends on whether it is an IO - intensive or CPU - intensive application. Comparison can be made in multi - goroutine tests.
go test -bench="." -benchmem -cpuprofile=cpu_profile.out -memprofile=mem_profile.out -benchtime=3s -count=3.
Enter fullscreen mode Exit fullscreen mode

Execution Results

BenchmarkReflectIsStructEmpty-16                 8127797               493 ns/op             112 B/op          7 allocs/op
BenchmarkReflectIsStructEmpty-16                 6139068               540 ns/op             112 B/op          7 allocs/op
BenchmarkReflectIsStructEmpty-16                 7282296               465 ns/op             112 B/op          7 allocs/op
BenchmarkNormalIsStructEmpty-16                 1000000000               0.272 ns/op           0 B/op          0 allocs/op
BenchmarkNormalIsStructEmpty-16                 1000000000               0.285 ns/op           0 B/op          0 allocs/op
BenchmarkNormalIsStructEmpty-16                 1000000000               0.260 ns/op           0 B/op          0 allocs/op
Enter fullscreen mode Exit fullscreen mode

Result Analysis

The meaning of the result fields:

Result Item Meaning
BenchmarkReflectIsStructEmpty - 16 BenchmarkReflectIsStructEmpty is the name of the test function, and - 16 indicates that the value of GOMAXPROCS (number of threads) is 16
2899022 A total of 2899022 executions were performed
401 ns/op Indicates that an average of 401 nanoseconds were spent per operation
112 B/op Indicates that 112 bytes of memory were allocated per operation
7 allocs/op Indicates that memory was allocated seven times

The time consumption of each operation judged by reflection is approximately 1000 times that of direct judgment, and it also brings seven additional memory allocations, increasing by 112 bytes each time. Overall, the performance still drops significantly compared to direct operation.

Copying Struct Fields with the Same Name Using Reflection

Implementation without Using Reflection

In actual business interfaces, we often need to convert data between DTO and VO, and most of the time it is the copying of fields with the same name. At this time, if we don't use reflection, we need to copy each field, and when a new struct needs to be copied, we need to repeat the writing of the new method as follows, which will bring a lot of repetitive work:

type aStruct struct {
    Name string
    Male string
}

type aStructCopy struct {
    Name string
    Male string
}

func newAStructCopyFromAStruct(a *aStruct) *aStructCopy {
    return &aStructCopy{
        Name: a.Name,
        Male: a.Male,
    }
}
Enter fullscreen mode Exit fullscreen mode

Implementation Using Reflection

Using reflection to copy structs, when there is a new struct that needs to be copied, we only need to pass in the struct pointer to copy fields with the same name. The implementation is as follows:

func CopyIntersectionStruct(src, dst interface{}) {
    sElement := reflect.ValueOf(src).Elem()
    dElement := reflect.ValueOf(dst).Elem()
    for i := 0; i < dElement.NumField(); i++ {
        dField := dElement.Type().Field(i)
        sValue := sElement.FieldByName(dField.Name)
        if!sValue.IsValid() {
            continue
        }
        value := dElement.Field(i)
        value.Set(sValue)
    }
}
Enter fullscreen mode Exit fullscreen mode

Performance Comparison

func BenchmarkCopyIntersectionStruct(b *testing.B) {
    a := &aStruct{
        Name: "test",
        Male: "test",
    }
    for i := 0; i < b.N; i++ {
        var ac aStructCopy
        CopyIntersectionStruct(a, &ac)
    }
}

func BenchmarkNormalCopyIntersectionStruct(b *testing.B) {
    a := &aStruct{
        Name: "test",
        Male: "test",
    }
    for i := 0; i < b.N; i++ {
        newAStructCopyFromAStruct(a)
    }
}
Enter fullscreen mode Exit fullscreen mode

Running Performance Tests

go test -bench="." -benchmem -cpuprofile=cpu_profile.out -memprofile=mem_profile.out -benchtime=3s -count=3.
Enter fullscreen mode Exit fullscreen mode

Running Results

BenchmarkCopyIntersectionStruct-16              10789202               352 ns/op              64 B/op          5 allocs/op
BenchmarkCopyIntersectionStruct-16              10877558               304 ns/op              64 B/op          5 allocs/op
BenchmarkCopyIntersectionStruct-16              10167404               322 ns/op              64 B/op          5 allocs/op
BenchmarkNormalCopyIntersectionStruct-16        1000000000               0.277 ns/op           0 B/op          0 allocs/op
BenchmarkNormalCopyIntersectionStruct-16        1000000000               0.270 ns/op           0 B/op          0 allocs/op
BenchmarkNormalCopyIntersectionStruct-16        1000000000               0.259 ns/op           0 B/op          0 allocs/op
Enter fullscreen mode Exit fullscreen mode

Similar to the first running result above, the time consumption of reflection is still 1000 times that of not using reflection, and the memory allocation also increases by 64 bytes each time. In actual business scenarios, multiple reflections may be combined. If you need to test the actual performance, you can write your own BenchmarkTest. Comparing flame graphs can more clearly show the proportion of running time.

Conclusion

In business interfaces, we assume that the interface response is 10ms, and the average operation of a reflection method is 400 nanoseconds, which will bring an additional memory allocation of approximately 64 - 112 bytes.

1ms [millisecond] = 1000μs [microsecond]=1000 * 1000ns [nanosecond]
1MB = 1024KB = 1024 * 1024 B
Enter fullscreen mode Exit fullscreen mode

If an interface performs 1000 reflection operations in the link, a single operation will increase the interface latency by approximately 0.4ms. Generally, the number of middleware and business operations in a single request rarely reaches this number, so the impact on the response time can basically be ignored. In actual business, more losses will be in memory copying and network IO.

However, reflection also has real problems in coding. It is more difficult to maintain and understand than ordinary business code. Therefore, we need to consider carefully when using it to avoid over - use, which will continuously increase the complexity of the code.

Leapcell: The Best Serverless Platform for Golang app Hosting

Image description

Finally, I would like to recommend the best platform for deploying Golang services: Leapcell

1. Multi - Language Support

  • Develop with JavaScript, Python, Go, or Rust.

2. Deploy unlimited projects for free

  • Pay only for usage — no requests, no charges.

3. Unbeatable Cost Efficiency

  • Pay - as - you - go with no idle charges.
  • Example: $25 supports 6.94M requests at a 60ms average response time.

4. Streamlined Developer Experience

  • Intuitive UI for effortless setup.
  • Fully automated CI/CD pipelines and GitOps integration.
  • Real - time metrics and logging for actionable insights.

5. Effortless Scalability and High Performance

  • Auto - scaling to handle high concurrency with ease.
  • Zero operational overhead — just focus on building.

Image description

Explore more in the documentation!

Leapcell Twitter: https://x.com/LeapcellHQ

Top comments (0)