Go 포인터의 용도
Go에는 포인터의 개념이 있습니다.
포인터를 통해 변수를 전달하면 값을 복사하지 않고 주소만 전달하면 되기 때문에 효율적이다. 그러나 고라면 포인터를 너무 많이 사용하면 낙타 컬렉션에 부담을 주고 CPU 시간을 많이 소모할 수 있다.
예를 들어 다음과 같은
S
구조체를 준비하고 복제한 후 되돌아오는 함수, 바늘로 되돌아오는 함수type S struct {
a, b, c int64
d, e, f string
g, h, i float64
}
func byCopy() S {
return S{
a: 1, b: 1, c: 1,
e: "foo", f: "foo",
g: 1.0, h: 1.0, i: 1.0,
}
}
func byPointer() *S {
return &S{
a: 1, b: 1, c: 1,
e: "foo", f: "foo",
g: 1.0, h: 1.0, i: 1.0,
}
}
준비된 함수를 다음 코드에 적어 기준을 얻는다.func BenchmarkStack(b *testing.B) {
var s S
f, err := os.Create("stack.out")
if err != nil {
panic(err)
}
defer f.Close()
err = trace.Start(f)
if err != nil {
panic(err)
}
for i := 0; i < b.N; i++ {
s = byCopy()
}
trace.Stop()
b.StopTimer()
_ = fmt.Sprintf("%v", s.a)
}
func BenchmarkMemoryHeap(b *testing.B) {
var s *S
f, err := os.Create("heap.out")
if err != nil {
panic(err)
}
defer f.Close()
err = trace.Start(f)
if err != nil {
panic(err)
}
for i := 0; i < b.N; i++ {
s = byPointer()
}
trace.Stop()
b.StopTimer()
_ = fmt.Sprintf("%v", s.a)
}
다음 결과를 얻을 수 있습니다(goversion go 1.15.6 linux/amd64)복제 함수 결과 향상
BenchmarkStack 127888153 9.41 ns/op 0 B/op 0 allocs/op
BenchmarkMemoryHeap 16394270 68.2 ns/op 96 B/op 1 allocs/op
Go: Should I Use a Pointer instead of a Copy of my Struct?(기준 인용원)
사용처
그러면 어떤 자리에서 지침을 사용해야 하나요?
함수 내에서 매개 변수와 수신기를 고쳐야 하는 상황
함수에서 매개 변수와 수신기를 다시 써야 한다면, 다시 쓰는 대상은 바늘로 전달해야 한다
type S struct { value string }
func (s S) SetA (v string) {
s.value = v
}
func (s *S) SetB (v string) {
s.value = v
}
func main() {
var s S
s.SetA("a")
fmt.Println(s.value) // sはゼロ値のまま
s.SetB("b")
fmt.Println(s.value) // b
}
goo play ground로 해볼게요. 반면 함수 내 수신기에 변경이 없는 함수는 값 수신기를 사용하는 측이 서명만으로'이 함수는 수신기에 변경이 추가되지 않는다'고 명확하게 표시하기 때문에 가독성 측면에서도 우수하다
복사를 피할 데이터를 매개 변수, 수신기로 설정할 때
os.File
또는 sync.Mutex
등 복사본이 발생한 후 문제가 발생할 수 있는 구조는 지침으로 처리하지 마십시오.// os.Open では File 型をポインタで返している
func Open(name string) (file *File, err error) {
return OpenFile(name, O_RDONLY, 0)
}
큰 구조나 배열을 처리할 때
큰 구조와 배열을 처리할 때 바늘을 사용하는 것이 더욱 효율적이다
반면
int
와string
같은 원시적인 유형과 필드가 많지 않은 구조체에 대해서는 복사 비용에 너무 신경 쓰지 말고 지침으로 처리하면 GC의 호환성과 효율성이 떨어지기 때문에 값 수신기를 사용하는 것이 좋다."Go Code Review Comments의""큰 음식""기준은 다음과 같습니다."
만약 구조체의 모든 값이 매개 변수에 전달된다고 가정한다면.너무 많이 느껴지면 바늘로 쓸 수 있는 크기다.
어려운 기준이지만 편의성, 변경 용이성, GC의 부하 등을 고려해 결정한다
망설일 때 수신기를 바늘 위에 놓아라.
이런 기술도 있는데, 만약 주저하면 바늘을 사용하는 것이 좋다
Go Code Review Comments 일본어 번역(Receiver Type)
큰 구조체를 절편에 유지하는 상황
슬라이스가
cap
이상append
에 도달하거나 for ~ range
에서 슬라이스 요소를 얻었을 때 모든 기록된 복사본이 발생하기 때문에 슬라이스 요소가 비교적 큰 구조체를 가지고 있다면 지침에 놓으면 복제 원가를 억제할 수 있다// listのcapが足りない場合新しくアロケートされる
list = append(list, x)
// xにlistの要素がコピーされる
for _, x := range list {
...
}
총결산
수치와 지침의 구분 기준이 내 마음속에 조금 명확해졌다
이렇게 보면 지침의 활약 위치는 상당히 제한되어 있다
이번에 GC의 부하에 대해 깊이 접촉하지 못했는데, 그 부근의 @imoty 보도는 매우 이해하기 쉽다
Go의 GC 지출이 높아지는 상황과 회피 전략
지침을 잘 사용해서 독자가 쉽게 뜻을 이해할 수 있는 보수성이 높은 코드를 써라
Reference
이 문제에 관하여(Go 포인터의 용도), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://zenn.dev/uji/articles/f6ab9a06320294146733텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)