일반적으로 copy of struct 보다는 pointer를 쓰는게 더 속도가 빠를거라 생각한다.
하지만 위 글은 모든 상황에서 그렇지는 않다는 내용을 다루고 있다.
아래는 그 상황들을 살펴볼건데 두 가지 case를 살펴볼거다.
다음 struct가 있을때,
type S struct {
a, b, c int64
d, e, f string
g, h, i float64
}
하나는 copy of struct를 반환하고 하나는 pointer를 반환한다.
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,
}
}
여기서 벤치마크를 만들어서 실행하면, (golang testing.B 모듈을 활용해서 만듦)
func BenchmarkMemoryStack(b *testing.B) {
var s S
f, err := os.Create("stack.out") // "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 = byCopy() // byPonter()
}
trace.Stop()
b.StopTimer()
_ = fmt.Sprintf("%v", s.a)
}
go test ./... -bench=BenchmarkMemoryHeap -benchmem -run=^$ -count=10 > head.txt && benchstat head.txt
go test ./... -bench=BenchmarkMemoryStack -benchmem -run=^$ -count=10 > stack.txt && benchstat stack.txt
copy of struct가 8배 더 빠른 결과를 가져옴.
왜냐?
위에서 만들어진 trace를 보면 확인할 수 있는데, pointer의 경우 heap 영역에 형성되기 때문에 garbage collector가 작동하게 된다.
( GC가 돌아가는 시간 + heap을 할당하는 system call이 합쳐져서 느린듯. )
결론적으로 heap 영역에 할당하는게 stack영역에 할당하는 것 보다 활씬 느리다.
GOMAXPROCS = 1 을 설정한 후 하면 더 느려진다.
struct에 빈 method를 추가할 것.
func (s S) stack(s1 S) {}
func (s *S) heap(s1 *S) {}
아래와 같은 벤치마크를 돌리면
func BenchmarkMemoryStack(b *testing.B) {
var s S
var s1 S
s = byCopy() // byPointer()
s1 = byCopy() // byPointer()
for i := 0; i < b.N; i++ {
for i := 0; i < 1000000; i++ {
s.stack(s1) // s.heap(s1)
}
}
}
이때는 pointer로 하는 연산이 약 2배정도 빠르다. (내 컴퓨터에서는 둘이 똑같다...)
왜냐? 앞에서 pointer가 느렸던 이유는 syscall + GC가 동작하기 때문이었는데 이제는 pointer는 할당은 한번만하고 계속 해당 주소를 공유해서 쓰는 반면, stack에서 돌아가는 경우 함수 호출마다 해당 struct를 복사해서 stack을 생성하기 때문에 느려진다.