15 minute read

개요

Go 1.21부터 추가된 clear 내장 함수는 맵과 슬라이스의 모든 요소를 효율적으로 제거합니다.

주요 특징:

  • 내장 함수: import 없이 사용 가능
  • 맵 지원: 모든 키-값 쌍 제거
  • 슬라이스 지원: 모든 요소를 제로 값으로 설정
  • 용량 유지: 기존 할당된 메모리 유지
  • 성능: 반복문보다 빠름
  • 타입 안전: 컴파일 타임 타입 체크
  • 간결성: 명확하고 읽기 쉬운 코드

기본 사용법

1. 맵에서 clear

package main

import "fmt"

func main() {
    // 맵 생성 및 초기화
    m := map[string]int{
        "apple":  1,
        "banana": 2,
        "orange": 3,
    }
    
    fmt.Println("Before clear:", m)
    fmt.Println("Length:", len(m))
    
    // 맵의 모든 요소 제거
    clear(m)
    
    fmt.Println("After clear:", m)
    fmt.Println("Length:", len(m))
    
    // 맵 재사용 가능
    m["grape"] = 4
    fmt.Println("After adding:", m)
}

출력:

Before clear: map[apple:1 banana:2 orange:3]
Length: 3
After clear: map[]
Length: 0
After adding: map[grape:4]

2. 슬라이스에서 clear

func main() {
    // 슬라이스 생성
    slice := []int{1, 2, 3, 4, 5}
    
    fmt.Println("Before clear:", slice)
    fmt.Println("Length:", len(slice))
    fmt.Println("Capacity:", cap(slice))
    
    // 슬라이스의 모든 요소를 제로 값으로 설정
    clear(slice)
    
    fmt.Println("After clear:", slice)
    fmt.Println("Length:", len(slice))
    fmt.Println("Capacity:", cap(slice))
}

출력:

Before clear: [1 2 3 4 5]
Length: 5
Capacity: 5
After clear: [0 0 0 0 0]
Length: 5
Capacity: 5

3. 포인터 슬라이스에서 clear

type Person struct {
    Name string
    Age  int
}

func main() {
    // 포인터 슬라이스
    people := []*Person{
        {Name: "Alice", Age: 30},
        {Name: "Bob", Age: 25},
        {Name: "Carol", Age: 35},
    }
    
    fmt.Println("Before clear:")
    for i, p := range people {
        if p != nil {
            fmt.Printf("  [%d] %+v\n", i, *p)
        }
    }
    
    // 포인터를 nil로 설정 (메모리 누수 방지)
    clear(people)
    
    fmt.Println("After clear:")
    for i, p := range people {
        fmt.Printf("  [%d] %v\n", i, p)
    }
}

출력:

Before clear:
  [0] {Name:Alice Age:30}
  [1] {Name:Bob Age:25}
  [2] {Name:Carol Age:35}
After clear:
  [0] <nil>
  [1] <nil>
  [2] <nil>

clear vs 다른 방법들

1. 맵 초기화 방법 비교

func main() {
    // 방법 1: clear 사용 (권장)
    m1 := map[string]int{"a": 1, "b": 2, "c": 3}
    clear(m1)
    fmt.Println("clear:", m1) // map[]
    
    // 방법 2: 새 맵으로 재할당
    m2 := map[string]int{"a": 1, "b": 2, "c": 3}
    m2 = make(map[string]int)
    fmt.Println("reassign:", m2) // map[]
    
    // 방법 3: 반복문으로 삭제
    m3 := map[string]int{"a": 1, "b": 2, "c": 3}
    for k := range m3 {
        delete(m3, k)
    }
    fmt.Println("loop delete:", m3) // map[]
}

비교:

  • clear: 가장 빠르고 명확, 메모리 재사용
  • 재할당: 새 메모리 할당, 이전 맵은 GC 대상
  • 반복문: 가장 느림, 각 키마다 delete 호출

2. 슬라이스 초기화 방법 비교

func main() {
    // 방법 1: clear 사용
    s1 := []int{1, 2, 3, 4, 5}
    clear(s1)
    fmt.Println("clear:", s1, "len:", len(s1), "cap:", cap(s1))
    // clear: [0 0 0 0 0] len: 5 cap: 5
    
    // 방법 2: 슬라이싱으로 길이 0
    s2 := []int{1, 2, 3, 4, 5}
    s2 = s2[:0]
    fmt.Println("slice:", s2, "len:", len(s2), "cap:", cap(s2))
    // slice: [] len: 0 cap: 5
    
    // 방법 3: 새 슬라이스로 재할당
    s3 := []int{1, 2, 3, 4, 5}
    s3 = make([]int, 0, 5)
    fmt.Println("reassign:", s3, "len:", len(s3), "cap:", cap(s3))
    // reassign: [] len: 0 cap: 5
    
    // 방법 4: 반복문으로 제로 값 설정
    s4 := []int{1, 2, 3, 4, 5}
    for i := range s4 {
        s4[i] = 0
    }
    fmt.Println("loop:", s4, "len:", len(s4), "cap:", cap(s4))
    // loop: [0 0 0 0 0] len: 5 cap: 5
}

차이점:

  • clear(s): 요소를 제로 값으로, 길이 유지
  • s[:0]: 길이를 0으로, 용량 유지, 요소는 그대로
  • make: 새 메모리 할당
  • 반복문: clear와 동일하지만 느림

성능 비교

1. 맵 벤치마크

package main

import (
    "testing"
)

const mapSize = 10000

func BenchmarkMapClear(b *testing.B) {
    m := make(map[int]int, mapSize)
    for i := 0; i < mapSize; i++ {
        m[i] = i
    }
    
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        clear(m)
        // 다시 채우기
        for j := 0; j < mapSize; j++ {
            m[j] = j
        }
    }
}

func BenchmarkMapReassign(b *testing.B) {
    m := make(map[int]int, mapSize)
    for i := 0; i < mapSize; i++ {
        m[i] = i
    }
    
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        m = make(map[int]int, mapSize)
        // 다시 채우기
        for j := 0; j < mapSize; j++ {
            m[j] = j
        }
    }
}

func BenchmarkMapDelete(b *testing.B) {
    m := make(map[int]int, mapSize)
    for i := 0; i < mapSize; i++ {
        m[i] = i
    }
    
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        for k := range m {
            delete(m, k)
        }
        // 다시 채우기
        for j := 0; j < mapSize; j++ {
            m[j] = j
        }
    }
}

예상 결과:

BenchmarkMapClear      5000    250000 ns/op
BenchmarkMapReassign   3000    450000 ns/op
BenchmarkMapDelete     2000    850000 ns/op

clear가 가장 빠름!

2. 슬라이스 벤치마크

const sliceSize = 10000

func BenchmarkSliceClear(b *testing.B) {
    s := make([]int, sliceSize)
    
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        clear(s)
    }
}

func BenchmarkSliceLoop(b *testing.B) {
    s := make([]int, sliceSize)
    
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        for j := range s {
            s[j] = 0
        }
    }
}

func BenchmarkSliceReslice(b *testing.B) {
    s := make([]int, sliceSize)
    
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        s = s[:0]
        s = s[:cap(s)]
    }
}

예상 결과:

BenchmarkSliceClear     50000    25000 ns/op
BenchmarkSliceLoop      30000    40000 ns/op
BenchmarkSliceReslice  100000    10000 ns/op (단, 요소는 그대로)

3. 메모리 할당 비교

func main() {
    var m runtime.MemStats
    
    // clear 사용
    runtime.ReadMemStats(&m)
    before := m.Alloc
    
    myMap := make(map[int]int)
    for i := 0; i < 100000; i++ {
        myMap[i] = i
    }
    clear(myMap)
    
    runtime.ReadMemStats(&m)
    after := m.Alloc
    fmt.Printf("clear: %d bytes\n", after-before)
    
    // 재할당 사용
    runtime.ReadMemStats(&m)
    before = m.Alloc
    
    myMap = make(map[int]int)
    for i := 0; i < 100000; i++ {
        myMap[i] = i
    }
    myMap = make(map[int]int)
    
    runtime.ReadMemStats(&m)
    after = m.Alloc
    fmt.Printf("reassign: %d bytes\n", after-before)
}

clear는 메모리를 재할당하지 않음!

메모리 동작

1. 맵의 메모리 동작

func main() {
    // 큰 맵 생성
    m := make(map[int]int)
    for i := 0; i < 1000000; i++ {
        m[i] = i
    }
    
    fmt.Println("Before clear, length:", len(m))
    
    // clear 후에도 내부 버킷은 유지됨
    clear(m)
    
    fmt.Println("After clear, length:", len(m))
    
    // 메모리는 여전히 할당되어 있어 빠르게 재사용 가능
    for i := 0; i < 1000000; i++ {
        m[i] = i
    }
    
    fmt.Println("After refill, length:", len(m))
}

2. 슬라이스의 메모리 동작

func main() {
    // 슬라이스 생성
    s := make([]int, 1000)
    for i := range s {
        s[i] = i
    }
    
    fmt.Printf("Before: len=%d, cap=%d, first=%v\n", 
        len(s), cap(s), s[0])
    
    // clear는 용량과 길이를 유지하고 요소만 제로 값으로
    clear(s)
    
    fmt.Printf("After: len=%d, cap=%d, first=%v\n", 
        len(s), cap(s), s[0])
    
    // 메모리 재사용 (재할당 없음)
    for i := range s {
        s[i] = i * 2
    }
    
    fmt.Printf("Refill: len=%d, cap=%d, first=%v\n", 
        len(s), cap(s), s[0])
}

출력:

Before: len=1000, cap=1000, first=0
After: len=1000, cap=1000, first=0
Refill: len=1000, cap=1000, first=0

3. 포인터 슬라이스와 GC

type LargeStruct struct {
    Data [1000]int
}

func main() {
    // 포인터 슬라이스
    s := make([]*LargeStruct, 1000)
    for i := range s {
        s[i] = &LargeStruct{}
    }
    
    fmt.Println("Created 1000 large structs")
    
    // clear 없이 슬라이싱만 하면 메모리 누수
    // s = s[:0] // ❌ 포인터는 여전히 참조됨
    
    // clear로 포인터를 nil로 설정 (GC 가능)
    clear(s) // ✅ 포인터가 nil이 되어 GC 가능
    s = s[:0]
    
    runtime.GC()
    fmt.Println("Memory can be reclaimed")
}

실전 예제

1. 캐시 초기화

type Cache struct {
    data map[string]interface{}
    mu   sync.RWMutex
}

func NewCache() *Cache {
    return &Cache{
        data: make(map[string]interface{}),
    }
}

func (c *Cache) Set(key string, value interface{}) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.data[key] = value
}

func (c *Cache) Get(key string) (interface{}, bool) {
    c.mu.RLock()
    defer c.mu.RUnlock()
    value, ok := c.data[key]
    return value, ok
}

func (c *Cache) Clear() {
    c.mu.Lock()
    defer c.mu.Unlock()
    clear(c.data) // 효율적인 초기화
}

func (c *Cache) Size() int {
    c.mu.RLock()
    defer c.mu.RUnlock()
    return len(c.data)
}

func main() {
    cache := NewCache()
    
    cache.Set("key1", "value1")
    cache.Set("key2", "value2")
    cache.Set("key3", "value3")
    
    fmt.Println("Size before clear:", cache.Size()) // 3
    
    cache.Clear()
    
    fmt.Println("Size after clear:", cache.Size()) // 0
}

2. 객체 풀 재사용

import "sync"

type Buffer struct {
    data []byte
}

var bufferPool = sync.Pool{
    New: func() interface{} {
        return &Buffer{
            data: make([]byte, 0, 4096),
        }
    },
}

func GetBuffer() *Buffer {
    return bufferPool.Get().(*Buffer)
}

func PutBuffer(buf *Buffer) {
    // 버퍼 내용 제거 (메모리는 유지)
    clear(buf.data)
    buf.data = buf.data[:0]
    bufferPool.Put(buf)
}

func main() {
    // 버퍼 가져오기
    buf := GetBuffer()
    
    // 사용
    buf.data = append(buf.data, []byte("Hello, World!")...)
    fmt.Println("Buffer:", string(buf.data))
    
    // 풀에 반환 (재사용)
    PutBuffer(buf)
    
    // 다시 가져오기 (같은 메모리 재사용 가능)
    buf2 := GetBuffer()
    fmt.Println("Reused buffer length:", len(buf2.data)) // 0
    fmt.Println("Reused buffer capacity:", cap(buf2.data)) // 4096
}

3. 배치 처리

type Processor struct {
    batch []int
    size  int
}

func NewProcessor(batchSize int) *Processor {
    return &Processor{
        batch: make([]int, 0, batchSize),
        size:  batchSize,
    }
}

func (p *Processor) Add(value int) {
    p.batch = append(p.batch, value)
    
    if len(p.batch) >= p.size {
        p.Process()
    }
}

func (p *Processor) Process() {
    if len(p.batch) == 0 {
        return
    }
    
    fmt.Printf("Processing batch of %d items: %v\n", len(p.batch), p.batch)
    
    // 배치 처리 후 초기화 (메모리 재사용)
    clear(p.batch)
    p.batch = p.batch[:0]
}

func (p *Processor) Flush() {
    p.Process()
}

func main() {
    processor := NewProcessor(5)
    
    for i := 1; i <= 12; i++ {
        processor.Add(i)
    }
    
    // 남은 항목 처리
    processor.Flush()
}

출력:

Processing batch of 5 items: [1 2 3 4 5]
Processing batch of 5 items: [6 7 8 9 10]
Processing batch of 2 items: [11 12]

4. 맵 재사용 패턴

type RequestHandler struct {
    headers map[string]string
}

func NewRequestHandler() *RequestHandler {
    return &RequestHandler{
        headers: make(map[string]string, 10), // 미리 할당
    }
}

func (h *RequestHandler) HandleRequest(req map[string]string) {
    // 이전 요청의 헤더 제거
    clear(h.headers)
    
    // 새 헤더 설정
    for k, v := range req {
        h.headers[k] = v
    }
    
    // 처리
    fmt.Println("Processing request with headers:", h.headers)
}

func main() {
    handler := NewRequestHandler()
    
    // 첫 번째 요청
    handler.HandleRequest(map[string]string{
        "Content-Type": "application/json",
        "Accept":       "application/json",
    })
    
    // 두 번째 요청 (맵 재사용)
    handler.HandleRequest(map[string]string{
        "Content-Type": "text/html",
        "User-Agent":   "MyApp/1.0",
    })
}

5. 슬라이스 워커 풀

type WorkerPool struct {
    workers []*Worker
}

type Worker struct {
    ID    int
    Tasks []Task
}

type Task struct {
    Name string
}

func NewWorkerPool(numWorkers int) *WorkerPool {
    workers := make([]*Worker, numWorkers)
    for i := range workers {
        workers[i] = &Worker{
            ID:    i,
            Tasks: make([]Task, 0, 100),
        }
    }
    return &WorkerPool{workers: workers}
}

func (wp *WorkerPool) AssignTask(workerID int, task Task) {
    if workerID >= 0 && workerID < len(wp.workers) {
        wp.workers[workerID].Tasks = append(wp.workers[workerID].Tasks, task)
    }
}

func (wp *WorkerPool) ProcessAll() {
    for _, worker := range wp.workers {
        worker.Process()
    }
}

func (w *Worker) Process() {
    if len(w.Tasks) == 0 {
        return
    }
    
    fmt.Printf("Worker %d processing %d tasks\n", w.ID, len(w.Tasks))
    
    // 작업 처리 후 초기화
    clear(w.Tasks)
    w.Tasks = w.Tasks[:0]
}

func main() {
    pool := NewWorkerPool(3)
    
    pool.AssignTask(0, Task{Name: "Task1"})
    pool.AssignTask(0, Task{Name: "Task2"})
    pool.AssignTask(1, Task{Name: "Task3"})
    pool.AssignTask(2, Task{Name: "Task4"})
    
    pool.ProcessAll()
}

6. 통계 수집기

type StatsCollector struct {
    samples []float64
    results map[string]float64
}

func NewStatsCollector() *StatsCollector {
    return &StatsCollector{
        samples: make([]float64, 0, 1000),
        results: make(map[string]float64),
    }
}

func (sc *StatsCollector) AddSample(value float64) {
    sc.samples = append(sc.samples, value)
}

func (sc *StatsCollector) Calculate() map[string]float64 {
    if len(sc.samples) == 0 {
        return sc.results
    }
    
    // 이전 결과 제거
    clear(sc.results)
    
    // 평균
    var sum float64
    for _, v := range sc.samples {
        sum += v
    }
    sc.results["mean"] = sum / float64(len(sc.samples))
    
    // 최소/최대
    min, max := sc.samples[0], sc.samples[0]
    for _, v := range sc.samples {
        if v < min {
            min = v
        }
        if v > max {
            max = v
        }
    }
    sc.results["min"] = min
    sc.results["max"] = max
    
    return sc.results
}

func (sc *StatsCollector) Reset() {
    clear(sc.samples)
    sc.samples = sc.samples[:0]
    clear(sc.results)
}

func main() {
    collector := NewStatsCollector()
    
    // 첫 번째 데이터 세트
    for _, v := range []float64{1.5, 2.3, 3.7, 4.2, 5.1} {
        collector.AddSample(v)
    }
    
    stats1 := collector.Calculate()
    fmt.Println("Stats 1:", stats1)
    
    // 리셋
    collector.Reset()
    
    // 두 번째 데이터 세트
    for _, v := range []float64{10.0, 20.0, 30.0} {
        collector.AddSample(v)
    }
    
    stats2 := collector.Calculate()
    fmt.Println("Stats 2:", stats2)
}

특수 케이스

1. nil 맵과 슬라이스

func main() {
    var m map[string]int
    var s []int
    
    // nil 맵이나 슬라이스에 clear 호출 시 패닉
    // clear(m) // panic: runtime error
    // clear(s) // panic: runtime error
    
    // 안전한 사용
    if m != nil {
        clear(m)
    }
    
    if s != nil {
        clear(s)
    }
    
    // 또는 make로 초기화
    m = make(map[string]int)
    s = make([]int, 10)
    
    clear(m) // OK
    clear(s) // OK
}

2. 빈 맵과 슬라이스

func main() {
    // 빈 맵
    m := make(map[string]int)
    clear(m) // OK, 아무 일도 일어나지 않음
    
    // 빈 슬라이스
    s := make([]int, 0)
    clear(s) // OK, 아무 일도 일어나지 않음
    
    // 길이 0인 슬라이스
    s2 := []int{1, 2, 3}
    s2 = s2[:0]
    clear(s2) // OK, 아무 일도 일어나지 않음 (길이가 0)
}

3. 중첩된 구조

func main() {
    // 맵의 맵
    m := map[string]map[string]int{
        "group1": {"a": 1, "b": 2},
        "group2": {"c": 3, "d": 4},
    }
    
    // 외부 맵만 clear (내부 맵은 그대로)
    clear(m)
    fmt.Println("Outer map:", m) // map[]
    
    // 중첩된 맵도 clear하려면
    m = map[string]map[string]int{
        "group1": {"a": 1, "b": 2},
        "group2": {"c": 3, "d": 4},
    }
    
    for _, inner := range m {
        clear(inner)
    }
    clear(m)
    
    // 슬라이스의 슬라이스
    s := [][]int{
        {1, 2, 3},
        {4, 5, 6},
    }
    
    // 외부 슬라이스만 clear
    clear(s) // [[0 0 0] [0 0 0]]로 변경 (포인터가 nil이 됨)
}

4. 구조체 슬라이스

type Person struct {
    Name string
    Age  int
}

func main() {
    people := []Person{
        {Name: "Alice", Age: 30},
        {Name: "Bob", Age: 25},
    }
    
    fmt.Println("Before:", people)
    
    // 각 요소가 제로 값으로
    clear(people)
    
    fmt.Println("After:", people)
    // After: [{Name: Age:0} {Name: Age:0}]
}

일반적인 실수

1. nil 체크 누락

// ❌ 나쁜 예
func clearMap(m map[string]int) {
    clear(m) // m이 nil이면 패닉!
}

// ✅ 좋은 예
func clearMap(m map[string]int) {
    if m != nil {
        clear(m)
    }
}

// ✅ 또는 새 맵 반환
func clearMap(m map[string]int) map[string]int {
    if m == nil {
        return make(map[string]int)
    }
    clear(m)
    return m
}

2. 슬라이스 길이 변경 기대

// ❌ 잘못된 기대
func main() {
    s := []int{1, 2, 3, 4, 5}
    clear(s)
    // clear는 길이를 변경하지 않음!
    fmt.Println(len(s)) // 여전히 5
    
    // 길이를 0으로 만들려면
    s = s[:0]
}

// ✅ 올바른 사용
func main() {
    s := []int{1, 2, 3, 4, 5}
    clear(s)      // 요소를 제로 값으로
    s = s[:0]     // 길이를 0으로
}

3. 포인터 슬라이스 메모리 누수

// ❌ 나쁜 예 (메모리 누수)
func main() {
    s := make([]*LargeObject, 1000)
    for i := range s {
        s[i] = &LargeObject{} // 큰 객체 할당
    }
    
    s = s[:0] // 길이만 0, 포인터는 여전히 참조됨!
}

// ✅ 좋은 예
func main() {
    s := make([]*LargeObject, 1000)
    for i := range s {
        s[i] = &LargeObject{}
    }
    
    clear(s)  // 포인터를 nil로 (GC 가능)
    s = s[:0]
}

4. 맵 재할당과 혼동

// ❌ 불필요한 재할당
func resetMap(m map[string]int) map[string]int {
    return make(map[string]int) // 새 메모리 할당
}

// ✅ clear 사용
func resetMap(m map[string]int) {
    clear(m) // 기존 메모리 재사용
}

5. 동시성 안전성 가정

// ❌ 나쁜 예 (경쟁 상태)
type Cache struct {
    data map[string]int
}

func (c *Cache) Clear() {
    clear(c.data) // 동시 접근 시 위험!
}

// ✅ 좋은 예
type Cache struct {
    data map[string]int
    mu   sync.RWMutex
}

func (c *Cache) Clear() {
    c.mu.Lock()
    defer c.mu.Unlock()
    clear(c.data)
}

6. 반환 후 clear

// ❌ 나쁜 예
func getAndClear() []int {
    s := []int{1, 2, 3}
    clear(s)
    return s // [0 0 0] 반환
}

// ✅ 의도에 맞게
func getAndClear() []int {
    s := []int{1, 2, 3}
    result := make([]int, len(s))
    copy(result, s)
    clear(s)
    return result // [1 2 3] 반환
}

7. 중첩 구조 부분 clear

// ❌ 불완전한 clear
func main() {
    m := map[string][]int{
        "a": {1, 2, 3},
        "b": {4, 5, 6},
    }
    
    clear(m) // 외부 맵만 clear, 내부 슬라이스는?
}

// ✅ 완전한 clear
func main() {
    m := map[string][]int{
        "a": {1, 2, 3},
        "b": {4, 5, 6},
    }
    
    // 내부 슬라이스도 clear
    for k := range m {
        clear(m[k])
    }
    clear(m)
}

베스트 프랙티스

1. nil 체크 함수

func ClearMap[K comparable, V any](m map[K]V) {
    if m != nil {
        clear(m)
    }
}

func ClearSlice[T any](s []T) {
    if s != nil {
        clear(s)
    }
}

// 사용
var myMap map[string]int
ClearMap(myMap) // 안전함

2. 메서드로 캡슐화

type Container struct {
    items []int
}

func (c *Container) Clear() {
    if c.items != nil {
        clear(c.items)
        c.items = c.items[:0]
    }
}

func (c *Container) Reset() {
    if c.items != nil {
        clear(c.items)
        c.items = c.items[:0:cap(c.items)]
    }
}

3. 재사용 패턴

type Reusable struct {
    buffer []byte
    cache  map[string]interface{}
}

func NewReusable() *Reusable {
    return &Reusable{
        buffer: make([]byte, 0, 4096),
        cache:  make(map[string]interface{}, 100),
    }
}

func (r *Reusable) Reset() {
    if r.buffer != nil {
        clear(r.buffer)
        r.buffer = r.buffer[:0]
    }
    if r.cache != nil {
        clear(r.cache)
    }
}

func (r *Reusable) Use() {
    // 사용
    r.buffer = append(r.buffer, []byte("data")...)
    r.cache["key"] = "value"
}

4. 풀과 함께 사용

var itemPool = sync.Pool{
    New: func() interface{} {
        return &Item{
            data: make([]byte, 0, 1024),
        }
    },
}

type Item struct {
    data []byte
}

func (i *Item) Reset() {
    clear(i.data)
    i.data = i.data[:0]
}

func GetItem() *Item {
    return itemPool.Get().(*Item)
}

func PutItem(item *Item) {
    item.Reset()
    itemPool.Put(item)
}

5. 문서화

// ClearCache removes all entries from the cache while preserving
// the allocated memory for reuse. This is more efficient than
// creating a new map.
//
// This method is safe to call on a nil cache.
func (c *Cache) ClearCache() {
    if c != nil && c.data != nil {
        clear(c.data)
    }
}

6. 테스트에서 활용

func TestHandler(t *testing.T) {
    handler := NewHandler()
    testCases := []struct {
        name  string
        input string
        want  string
    }{
        {"case1", "input1", "output1"},
        {"case2", "input2", "output2"},
    }
    
    for _, tc := range testCases {
        t.Run(tc.name, func(t *testing.T) {
            // 각 테스트 전 초기화
            handler.Clear()
            
            got := handler.Process(tc.input)
            if got != tc.want {
                t.Errorf("got %v, want %v", got, tc.want)
            }
        })
    }
}

7. 성능 최적화

// 고빈도 호출 함수에서 재사용
type Processor struct {
    tempBuffer []int
}

func NewProcessor() *Processor {
    return &Processor{
        tempBuffer: make([]int, 0, 1000), // 미리 할당
    }
}

func (p *Processor) Process(data []int) []int {
    // 임시 버퍼 초기화 (재할당 없음)
    clear(p.tempBuffer)
    p.tempBuffer = p.tempBuffer[:0]
    
    // 처리 로직
    for _, v := range data {
        if v > 0 {
            p.tempBuffer = append(p.tempBuffer, v*2)
        }
    }
    
    // 결과 복사
    result := make([]int, len(p.tempBuffer))
    copy(result, p.tempBuffer)
    
    return result
}

8. 조건부 clear

func (c *Cache) ClearIfFull() bool {
    if len(c.data) >= c.maxSize {
        clear(c.data)
        return true
    }
    return false
}

func (b *Buffer) ClearIfStale(maxAge time.Duration) bool {
    if time.Since(b.lastUpdate) > maxAge {
        clear(b.data)
        b.data = b.data[:0]
        return true
    }
    return false
}

정리

  • 기본: 맵의 모든 항목 제거, 슬라이스 요소를 제로 값으로
  • 성능: 재할당보다 빠름, 메모리 재사용
  • 메모리: 용량 유지, GC 압력 감소
  • vs 다른 방법: clear > 재할당 > 반복문
  • 슬라이스: clear + [:0]로 완전 초기화
  • 포인터: clear로 nil 설정하여 메모리 누수 방지
  • 실전: 캐시, 풀, 배치, 통계, 워커
  • 실수: nil 체크 누락, 길이 변경 기대, 메모리 누수, 동시성
  • 베스트: nil 체크, 캡슐화, 재사용, 풀, 문서화, 테스트