[Go] 메모리 관리에 대해서 알아보자 본문

Go에서 메모리는 크게 두 곳에 할당된다. 스택은 함수가 호출될 때 자동으로 생성되고 반환 시 즉시 사라지는 임시 공간으로 사용되고, 힙은 장기적으로 보관되어야 하는 데이터가 머무는 공간으로 사용된다.
장기적인 보관이라 함은, "변수가 함수의 범위를 벗어나도 살아남아야 한다." 정도로 생각하면 쉽게 이해할 수 있다. 함수의 범위에서 벗어나 힙에 할당되는 것을 escape 라고 부른다.
스택 메모리는 자동으로 해제되고 속도가 빠르며, 크기 제한이 존재한다. 주로 함수 내부에 정의된 지역 변수, 함수 파라미터가 스택 메모리에 저장된다.
힙 메모리는 GC를 통해 관리되어 속도가 느리지만, 크기가 스택 메모리에 비해 유연하다. 주로 포인터 타입으로 반환된 변수, 클로저 캡처 등이 저장된다.

Go는 선언된 변수가 힙 메모리에 저장되어야 하는지 스택에 남아있어야 하는지를 컴파일 타임에 결정하는데, 이를 Escape Analysis라고 한다.
개발자가 직접 malloc을 선언하는 C와 다르게, Go는 이 결정을 전적으로 컴파일러에게 맡긴다.
go build -gcflags="-m" 명령어를 사용하면 어떤 변수 혹은 객체 등이 escape 되는지 콘솔을 통해 확인할 수 있다.

위의 코드를 기반으로 go build -gcflags="-m" 명령어를 실행시키면 아래처럼 Escape Analysis가 발생하는 것을 확인할 수 있다.

콘솔에 출력된 내용을 천천히 살펴보자.
우선, newUser가 인라인된 것을 확인할 수 있다. 인라인은 함수 호출을 없애는 최적화로 newUser 함수 호출을 없애고 함수의 본문을 직접 호출 지점에 삽입하는 것을 의미한다.
인라인을 통해 함수 호출을 없애면 스택 프레임 생성/해제 오버헤드 비용이 줄어든다. 즉, Go 컴파일러가 newUser 함수를 단순한 함수라고 판단하여 최적화를 진행했다는 것을 알 수 있다.
하지만 신기하게도 fmt.Println() 함수도 인라인이 됐는데, fmt.Println() 함수는 사실 Fprintln(os.Stdout, a...) 함수를 감싸고 있는 래퍼(Wrapper)이기 때문에 실질적으로 동작을 수행하는 Fprintln(os.Stdout, a...) 함수가 제거된 것이 아니라 래퍼인 fmt.Println()의 호출만 제거된 것이다.
name 파라미터에는 leak이라는 언급이 붙는데, 이는 해당 파라미터가 u에 할당되어 함수 바깥으로 벗어난다는 의미로 사용된다. 메모리 누수가 아니다.
가장 핵심인 newUser 함수의 지역변수인 u가 힙에 할당되었다는 메세지다. 해당 함수가 &u로 변수가 저장된 메모리의 주소를 반환하기 때문에, 함수가 끝나도 u가 살아있어야 한다고 판단했기에 힙 메모리로 이동했다는 것이다.
...은 fmt.Println()에서 사용되는 가변인자 슬라이스(...interface{})인데, 힙이 아닌 스택 메모리에서 처리된다고 표시된다.
마지막으로 p.Name은 fmt.Println()의 interface{}로 전달되어 boxing 되었기 때문에 힙으로 할당되었다.
Go의 컴파일러는 CPU의 월드 사이즈에 맞춰 구조체 필드 사이에 패딩을 자동으로 삽입하는데, 필드 순서를 잘 배치하는 것으로 구조체가 할당되는 메모리의 크기를 줄일 수 있다.
보통 64bit 컴퓨터의 월드 사이즈는 8 바이트이기 때문에 구조체의 필드를 8 바이트 기준으로 정렬하면 메모리 효율을 최적화 할 수 있다.

BadStruct 구조체가 할당된 메모리의 경우

무려 17bytes의 패딩이 들어가 실제 데이터의 크기인 15bytes보다 더 큰 패딩이 추가된 것을 볼 수 있다. 이렇게 되면 32bytes의 메모리를 사용하는 반면,
GoodStruct 구조체가 할당된 메모리의 경우

필드의 순서를 다르게 한 것만으로도 32bytes였던 구조체의 크기를 16bytes로 50%나 줄일 수 있다.
'Dev' 카테고리의 다른 글
| [Go] 컨텍스트(Context) (0) | 2026.04.07 |
|---|---|
| [Go] 고루틴(Goroutine) & 채널(Channel) (0) | 2026.03.27 |
| 단일 트랜잭션 유지 그리고 PGMQ (2) | 2024.12.15 |
| [Go] 함수 고급 (0) | 2024.12.02 |
| [Go] 인터페이스 (5) | 2024.11.10 |