import ( "코딩", "행복", "즐거움" )

Go 리플렉션 성능이슈 본문

GO

Go 리플렉션 성능이슈

더코드마니아 2022. 11. 16. 18:32

원문: https://www.sobyte.net/post/2021-07/go-reflect-performance/

 

Go Reflect Performance

Go reflect package provides the ability to get the type and value of an object at runtime, which can help us to abstract and simplify the code, achieve dynamic data acquisition and method invocation, improve development efficiency and readability, and make

www.sobyte.net

Go에도 Java나 C# 과 같이 리플렉션 기능을 지원하는 패키지를 지원한다. 

reflect는 런타임에 개체의 유형과 값을 가져올 수 있는 기능을 제공한다. relect를 이용하여 일반적인 코드 기법들을 개발할 수 있어 매우 유용하지만 성능이라는 댓가를 지불해야 한다고 알려져있다. 

( reflect 객체 생성 및 획득 은 추가적인 코드 명령들을 추가하고 interface{} boxing/unboxing 작업도 포함되며, 중간에 임시 개체 생성을 증가 시켜 성능을 저하시킴 ) 

 

Java나 C#은 reflect 개체를 미리 캐시하여 성능에 대한 영향을 줄일 수 있다. 

 

아래는 개체를 생성하는 테스트와 생성된 개체의 필드에 값을 할당하는 테스트 코드 이다. 

package test
import (
	"reflect"
	"testing"
)
type Student struct {
	Name  string
	Age   int
	Class string
	Score int
}
func BenchmarkReflect_New(b *testing.B) {
	var s *Student
	sv := reflect.TypeOf(Student{})
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		sn := reflect.New(sv)
		s, _ = sn.Interface().(*Student)
	}
	_ = s
}
func BenchmarkDirect_New(b *testing.B) {
	var s *Student
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		s = new(Student)
	}
	_ = s
}
func BenchmarkReflect_Set(b *testing.B) {
	var s *Student
	sv := reflect.TypeOf(Student{})
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		sn := reflect.New(sv)
		s = sn.Interface().(*Student)
		s.Name = "Jerry"
		s.Age = 18
		s.Class = "20005"
		s.Score = 100
	}
}
func BenchmarkReflect_SetFieldByName(b *testing.B) {
	sv := reflect.TypeOf(Student{})
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		sn := reflect.New(sv).Elem()
		sn.FieldByName("Name").SetString("Jerry")
		sn.FieldByName("Age").SetInt(18)
		sn.FieldByName("Class").SetString("20005")
		sn.FieldByName("Score").SetInt(100)
	}
}
func BenchmarkReflect_SetFieldByIndex(b *testing.B) {
	sv := reflect.TypeOf(Student{})
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		sn := reflect.New(sv).Elem() 
		sn.Field(0).SetString("Jerry")
		sn.Field(1).SetInt(18)
		sn.Field(2).SetString("20005")
		sn.Field(3).SetInt(100)
	}
}
func BenchmarkDirect_Set(b *testing.B) {
	var s *Student
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		s = new(Student)
		s.Name = "Jerry"
		s.Age = 18
		s.Class = "20005"
		s.Score = 100
	}
}

시험 결과는 아래와 같다. 

BenchmarkReflect_New-4               	20000000	        70.0 ns/op	      48 B/op	       1 allocs/op
BenchmarkDirect_New-4                	30000000	        45.6 ns/op	      48 B/op	       1 allocs/op
BenchmarkReflect_Set-4               	20000000	        73.6 ns/op	      48 B/op	       1 allocs/op
BenchmarkReflect_SetFieldByName-4    	 3000000	       492 ns/op	      80 B/op	       5 allocs/op
BenchmarkReflect_SetFieldByIndex-4   	20000000	       111 ns/op	      48 B/op	       1 allocs/op
BenchmarkDirect_Set-4                   30000000	        43.1 ns/op

결과는 역시 reflect를 사용하지 않고 Direct로 할당하고, 셋 하는 작업이 월등이 빠름을 알 수 있다. 

특히 FieldByName 메서드의 성능 수준은 최악이다.

 

성능이 필요한 부분에서는 reflect의 사용을 지양하고, interface{} 통한 형변환 호출을 자제해야 한다. 

아래 코드는 interface{}를 통한 형변환 호출에 대한 성능 수준을 테스트 한것이다. 

func DirectInvoke(s *Student) {
	s.Name = "Jerry"
	s.Age = 18
	s.Class = "20005"
	s.Score = 100
}
func InterfaceInvoke(i interface{}) {
	s := i.(*Student)
	s.Name = "Jerry"
	s.Age = 18
	s.Class = "20005"
	s.Score = 100
}
func BenchmarkDirectInvoke(b *testing.B) {
	s := new(Student)
	for i := 0; i < b.N; i++ {
		DirectInvoke(s)
	}
	_ = s
}
func BenchmarkInterfaceInvoke(b *testing.B) {
	s := new(Student)
	for i := 0; i < b.N; i++ {
		InterfaceInvoke(s)
	}
	_ = s
}

결과

BenchmarkDirectInvoke-4              	300000000	         5.60 ns/op	       0 B/op	       0 allocs/op
BenchmarkInterfaceInvoke-4           	200000000	         6.64 ns/op

interface{} 개체를 변환하는 작업은 성능에 큰 영향은 아니지만 미미하게 차이가 발생함을 알 수 있다. 

 

'GO' 카테고리의 다른 글

Golang sync.Pool 기본 원리  (0) 2023.03.03
고루틴??  (0) 2023.02.23
GO 백엔드 개발자 학습 로드맵  (0) 2022.11.16
GO 디자인 패턴에 관심이 있다면 ??  (0) 2022.11.16
Golang 벤치마킹 함수 성능 개선  (0) 2022.11.15