피드로 돌아가기
Go BlogBackend
원문 읽기
Allocating on the Stack
Go 컴파일러가 스택 할당 최적화를 도입해 힙 할당으로 인한 가비지 컬렉션 오버헤드 제거
AI 요약
Context
Go 프로그램의 힙 할당은 상당한 양의 할당자 코드 실행을 필요로 하며, 가비지 컬렉터에 추가 부하를 가한다. 동적 크기의 슬라이스 빌드 시 초기 단계에서 append 호출이 반복적으로 메모리를 재할당하면서 불필요한 가비지를 생성한다.
Technical Solution
- Go 1.25의 조건부 상수 크기 할당: 길이 추정값이 임계값 이하일 때 make() 호출을 상수 크기로 처리해 컴파일러가 스택 할당을 자동으로 선택하도록 개선
- Go 1.26의 동적 버퍼 승격: 스택 할당 버퍼에서 시작하다가 용량 초과 시에만 힙으로 승격하는 메커니즘 도입
- 슬라이스 반환 시 동적 복사: 스택 할당 슬라이스가 반환될 때만 힙에 복사본을 생성해 필요한 경우에만 할당+복사 수행
- 상수 크기 슬라이스의 스택 할당: make([]task, 0, 10) 형태로 상수 크기를 명시하면 컴파일러가 backing store를 스택 프레임에 할당
- 최적화 비활성화 옵션: -gcflags=all=-d=variablemakehash=n 플래그로 스택 할당 최적화를 사용 중지할 수 있음
Impact
상수 크기 슬라이스 할당 시 할당 횟수를 1회(make 호출)에서 0회로 감소시켜 힙 할당으로 인한 가비지 컬렉션 부하 완전 제거. Go 1.26의 동적 버퍼 승격 방식은 수동 최적화된 코드의 추가 할당+복사 오버헤드를 제거하면서도 초기 단계의 복사 비용으로 상쇄.
Key Takeaway
Go 컴파일러의 자동 스택 할당 최적화는 개발자가 어려운 슬라이스 크기 추정 없이도 가비지 컬렉션 오버헤드를 제거할 수 있으며, 예측 가능한 크기의 컬렉션은 명시적 상수 크기 할당으로 추가 최적화를 얻을 수 있다.
실천 포인트
Go 프로그램에서 루프 내 append를 사용해 슬라이스를 구성할 때, 예상 크기를 파악하면 make([]T, 0, estimatedSize)로 초기 용량을 명시하면 된다. Go 1.25 이상에서는 작은 크기(예: 10 이하)의 상수값을 사용하면 컴파일러가 자동으로 스택 할당을 선택해 힙 할당 제로(0-allocation)를 달성할 수 있고, 예상 크기가 가변적이면 컴파일러가 적응적으로 스택에서 시작했다가 필요 시 힙으로 승격해 가비지 컬렉션 오버헤드를 최소화한다.