The Ultimate Go Study Guide의 내용을 참고하여 작성했습니다.
이스케이프 분석이란 객체의 포인터(참조)가 서브 루틴 밖으로 전파되는지를 분석하는 기술입니다. 이를 통해서 컴파일러가 최적화를 수행할 때 객체의 메모리를 스택과 힙 중 한 곳에 할당합니다.
type user struct { name string } func stayOnStack() user { // 스택에 할당됨 u := user{ name: "Gump", } return u } func escapeToHeap() *user { // 힙에 할당됨 u := user{ name: "Gump", } // 이스케이프 분석을 통해 포인터가 리턴됨을 인지하고 객체를 스택이 아닌 힙에 할당함 return &u }
위 예제에서 stayOnStack()
함수에서 생성된 객체 u
는 리턴 시 객체의 사본이 리턴 됩니다. 이 때문에 stayOnStack()
함수 밖에서는 객체 u
를 사용할 수 없습니다. 컴파일 시점에 객체 u의 메모리 크기를 알 수 있기 때문에 컴파일러는 객체 u를 스택에 할당합니다.
반면 escapeToHeap()
함수에서는 객체의 포인터를 리턴합니다. 컴파일러는 이스케이프 분석을 통해 포인터가 리턴됨을 인지하고 객체를 스택이 아닌 힙에 할당하도록 명령합니다.
빌드 시 아래 옵션을 사용하면 이스케이프 분석 정보를 확인할 수 있습니다.
go build -gcflags="-m"
고루틴 스택은 초기에 2KB의 메모리만 할당받습니다. 함수를 호출하면 가장 먼저 스택 공간이 충분한지 확인합니다. 부족할 경우 더 큰 메모리를 할당한 후 값을 복사합니다. 메모리가 부족할 때 마다 값을 복사하는 것은 비효율 적이지만, 고루틴에 스택을 적게 할당하여 얻는 이점이 더 큽니다.
스택이 커질 수 있기 때문에 하나의 고루틴이 다른 고루틴의 스택 메모리에 대한 포인터를 가질 수 없습니다. 컴파일러가 모든 포인터를 추적하는 것은 지나친 과부하가 되어 지연 시간이 엄청나게 커질 수 있습니다.
이 때문에 고루틴 간에 스택을 공유하지 않습니다.
힙에 저장한 객체가 더 이상 사용하지 않게 되었을 때 가비지 콜렉터가 개입합니다. 가비지 콜렉터에게 가장 중요한 것은 페이싱 알고리즘입니다. 가비지 컬렉션에 걸리는 시간을 최소화 하기위해서 어떤 주기와 페이스로 가비지 컬렉션을 실행할 지를 결정해야 합니다.
4MB 힙을 가진 프로그램에서는 가비지 콜렉터가 라이브 힙을 2MB로 유지하려고 합니다. 라이브 힙이 4MB를 넘어서면 어 큰 힙을 할당해줘야 하기 때문입니다. 가비지 콜렉터는 라이브 힙을 줄여주어야 하기 때문에 페이스는 힙이 얼마나 빠르게 커지는지에 달려있습니다.
GC가 작동할 때는 성능이 떨어질 수밖에 없습니다. 그래야 모든 고루틴이 동시에 작동할 수 있다. GC 역시 가비지 컬렉션 작업을 하는 고루틴들을 실행시키며, 가용 CPU 의 25%를 사용한다. 다음은 runtime.mgc.go 파일의 설정 내용입니다.
// gcBackgroundUtilization is the fixed CPU utilization for background // marking. It must be <= gcGoalUtilization. The difference between // gcGoalUtilization and gcBackgroundUtilization will be made up by // mark assists. The scheduler will aim to use within 50% of this // goal. // // Setting this to < gcGoalUtilization avoids saturating the trigger // feedback controller when there are no assists, which allows it to // better control CPU and heap growth. However, the larger the gap, // the more mutator assists are expected to happen, which impact // mutator latency. const gcBackgroundUtilization = 0.25