MemoryOptimization 완벽 마스터
MemoryOptimization의 핵심 개념과 실전 활용법
학습 항목
이미지 로딩 중...
Golang 성능 최적화 가이드
Go 언어의 성능을 극대화하기 위한 핵심 기법들을 소개합니다. 메모리 관리, 동시성 처리, 프로파일링 등 실무에서 바로 적용 가능한 최적화 기법을 배울 수 있습니다.
들어가며
이 글에서는 Golang 성능 최적화 가이드에 대해 상세히 알아보겠습니다. 총 10가지 주요 개념을 다루며, 각각의 개념에 대한 설명과 실제 코드 예제를 함께 제공합니다.
목차
- 슬라이스_사전할당
- 문자열_연결_최적화
- 고루틴_풀_패턴
- 포인터_vs_값_전달
- 맵_사전할당
- sync.Pool_재사용
- 버퍼_채널_활용
- pprof_프로파일링
- 인라인_함수_최적화
- 불필요한_인터페이스_회피
1. 슬라이스_사전할당
개요
슬라이스의 크기를 미리 할당하여 불필요한 메모리 재할당을 방지하고 성능을 향상시킵니다.
코드 예제
// 비효율적
data := []int{}
for i := 0; i < 1000; i++ {
data = append(data, i)
}
// 효율적
data := make([]int, 0, 1000)
for i := 0; i < 1000; i++ {
data = append(data, i)
}
설명
make 함수로 용량을 미리 지정하면 append 시 메모리 재할당이 발생하지 않아 약 2-3배 빠릅니다.
2. 문자열_연결_최적화
개요
여러 문자열을 연결할 때 strings.Builder를 사용하면 메모리 효율이 크게 향상됩니다.
코드 예제
import "strings"
// 비효율적
result := ""
for i := 0; i < 100; i++ {
result += "text"
}
// 효율적
var builder strings.Builder
for i := 0; i < 100; i++ {
builder.WriteString("text")
}
result := builder.String()
설명
Builder는 내부 버퍼를 재사용하여 + 연산자보다 10배 이상 빠르고 메모리 할당도 적습니다.
3. 고루틴_풀_패턴
개요
무제한 고루틴 생성 대신 워커 풀을 사용하여 리소스를 효율적으로 관리합니다.
코드 예제
func workerPool(jobs <-chan int, results chan<- int) {
for i := 0; i < 5; i++ {
go func() {
for job := range jobs {
results <- job * 2
}
}()
}
}
jobs := make(chan int, 100)
results := make(chan int, 100)
workerPool(jobs, results)
설명
고루틴 개수를 제한하여 과도한 컨텍스트 스위칭을 방지하고 메모리 사용량을 안정적으로 유지합니다.
4. 포인터_vs_값_전달
개요
큰 구조체는 포인터로 전달하여 복사 오버헤드를 줄이고 성능을 개선합니다.
코드 예제
type LargeStruct struct {
data [1000]int
}
// 비효율적 (값 복사)
func process(s LargeStruct) {
// 처리 로직
}
// 효율적 (포인터)
func process(s *LargeStruct) {
// 처리 로직
}
설명
구조체가 클수록 포인터 전달이 유리하며, 작은 구조체는 값 전달이 캐시 효율성 면에서 더 나을 수 있습니다.
5. 맵_사전할당
개요
맵의 예상 크기를 미리 지정하여 동적 확장으로 인한 성능 저하를 방지합니다.
코드 예제
// 비효율적
m := make(map[string]int)
for i := 0; i < 1000; i++ {
m[fmt.Sprintf("key%d", i)] = i
}
// 효율적
m := make(map[string]int, 1000)
for i := 0; i < 1000; i++ {
m[fmt.Sprintf("key%d", i)] = i
}
설명
맵 크기를 사전에 지정하면 해시 테이블 재할당 횟수가 줄어 성능이 향상됩니다.
6. sync.Pool_재사용
개요
자주 생성/삭제되는 객체를 sync.Pool로 재사용하여 GC 부담을 줄입니다.
코드 예제
var bufferPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
buf := bufferPool.Get().(*bytes.Buffer)
buf.Reset()
defer bufferPool.Put(buf)
buf.WriteString("temporary data")
설명
Pool을 사용하면 객체 생성 비용과 가비지 컬렉션 압력을 대폭 줄일 수 있습니다.
7. 버퍼_채널_활용
개요
버퍼를 가진 채널을 사용하여 송수신 블로킹을 줄이고 처리량을 높입니다.
코드 예제
// 버퍼 없음 (블로킹 가능)
ch := make(chan int)
// 버퍼 있음 (논블로킹)
ch := make(chan int, 100)
go func() {
for i := 0; i < 100; i++ {
ch <- i // 버퍼 범위 내 블로킹 없음
}
}()
설명
적절한 버퍼 크기는 고루틴 간 통신 대기 시간을 줄여 전체 처리 속도를 향상시킵니다.
8. pprof_프로파일링
개요
pprof를 사용하여 CPU와 메모리 사용을 분석하고 병목 지점을 찾습니다.
코드 예제
import _ "net/http/pprof"
import "net/http"
func main() {
go func() {
http.ListenAndServe("localhost:6060", nil)
}()
// 애플리케이션 코드
}
// 터미널: go tool pprof http://localhost:6060/debug/pprof/profile
설명
브라우저나 CLI로 실시간 프로파일링 데이터를 확인하여 최적화가 필요한 부분을 정확히 파악할 수 있습니다.
9. 인라인_함수_최적화
개요
작은 함수는 컴파일러가 자동으로 인라인화하여 함수 호출 오버헤드를 제거합니다.
코드 예제
// 인라인 가능 (작고 단순)
func add(a, b int) int {
return a + b
}
// 인라인 불가능 (복잡)
func complexCalc(data []int) int {
sum := 0
for _, v := range data {
sum += v * v
}
return sum
}
설명
간단한 함수는 인라인화되어 성능이 향상되며, go build -gcflags="-m" 명령으로 확인할 수 있습니다.
10. 불필요한_인터페이스_회피
개요
성능이 중요한 부분에서는 구체 타입을 사용하여 동적 디스패치 비용을 줄입니다.
코드 예제
// 느림 (인터페이스)
func process(r io.Reader) {
// 가상 메서드 호출
}
// 빠름 (구체 타입)
func process(f *os.File) {
// 직접 메서드 호출
}
설명
인터페이스는 유연성을 제공하지만 간접 호출 비용이 있으므로, 핫패스에서는 구체 타입 사용을 고려하세요.
마치며
이번 글에서는 Golang 성능 최적화 가이드에 대해 알아보았습니다. 총 10가지 개념을 다루었으며, 각각의 사용법과 예제를 살펴보았습니다.
관련 태그
#Go #Performance #Concurrency #MemoryOptimization #Profiling