Reduce GC

Trả lời với góc nhìn của Senior Golang Developer, câu hỏi:

"What are some strategies to reduce GC (Garbage Collection) overhead?"

Đây là vấn đề tối ưu hiệu năng cấp thấp, cực kỳ quan trọng khi xây dựng high-performance backend systems, đặc biệt là service throughput cao và low-latency (như trong game servers, pub/sub, hoặc real-time analytics).


✅ 1. Giảm allocation - "Don’t feed the GC"

GC chỉ phải làm việc nhiều nếu bạn tạo nhiều rác (allocations → garbage).

Chiến lược:

  • Tránh tạo object ngắn sống nhiều lần bên trong loop.

  • Tái sử dụng struct hoặc slice thay vì luôn tạo mới.

// BAD: allocates every time
for i := 0; i < 1000; i++ {
    b := make([]byte, 1024)
    doSomething(b)
}

// GOOD: reuse
buf := make([]byte, 1024)
for i := 0; i < 1000; i++ {
    doSomething(buf)
}

✅ 2. Sử dụng sync.Pool để tái sử dụng object

sync.Pool là một object pool dùng để cache object tạm thời.

var bufPool = sync.Pool{
    New: func() any {
        return make([]byte, 1024)
    },
}

b := bufPool.Get().([]byte)
defer bufPool.Put(b)

Dùng khi bạn có object lớn, tạm thời, dùng nhiều lần. Tránh alloc liên tục.


✅ 3. Tránh tạo string không cần thiết

Golang string là immutable, mọi thao tác tạo string mới = allocation.

  • Hạn chế fmt.Sprintf hoặc string([]byte) nhiều lần.

  • Dùng strings.Builder khi cần build string lớn.


✅ 4. Giảm con trỏ không cần thiết

Struct có con trỏ (*T) hoặc field dạng con trỏ sẽ phải quản lý bởi GC.

type Person struct {
    Name string // GOOD
    Age  int    // GOOD
}

type Bad struct {
    Name *string // BAD – GC phải scan & mark
}

Dùng giá trị trực tiếp thay vì con trỏ nếu không cần nullable.


✅ 5. Tối ưu slice & map

  • Slice: Preallocate capacity khi có thể.

  • Map: Dùng make(map[T]V, capacity) để tránh rehash liên tục.

  • Tránh tạo map tạm thời ngắn hạn lặp đi lặp lại.


✅ 6. Tối ưu lifetime object – Escape Analysis

Biến nằm trong stack thì GC không cần quản lý (free ngay khi hàm return).

Chiến lược:

  • Tránh return con trỏ đến local struct.

  • Tránh dùng interface khiến giá trị bị “escape” ra heap.

func newUser() *User { // This escapes to heap
    return &User{Name: "Tài"}
}

✅ 7. Giảm áp lực GC bằng cách scale horizontal

  • Phân tán workload sang nhiều instance → GC mỗi process nhẹ hơn.

  • Thường dùng trong system chịu tải nặng (microservices, load balancing).


✅ 8. Dùng -gcflags và profiling để tune GC

  • GODEBUG=gctrace=1 → để xem log GC.

  • Dùng pprof để xem số lần GC, memory allocated.

  • Flag build: go build -gcflags "-m" để xem biến nào escape to heap.


✅ 9. Tránh tạo closure trong loop nếu không cần

Closure thường làm biến bị escape to heap nếu không tối ưu.

for i := 0; i < 10; i++ {
    go func() { fmt.Println(i) }() // BAD: i captured
}

✅ 10. Sử dụng Arena (Go 1.22 experimental)

Go 1.22 giới thiệu arena allocation (tạm thời). Dùng cho batch jobs mà bạn có thể free toàn bộ một khối memory cùng lúc (manual memory management).

a := arena.New()
defer a.Free()
b := arena.MakeSlice[byte](a, 100)

⚠️ Experimental – chưa production-stable nhưng tương lai có thể thay đổi game cho hiệu năng.


🧠 Kết luận:

Mục tiêu
Chiến lược chính

Giảm rác (garbage)

Hạn chế allocation, reuse object

Giảm tần suất GC

Dùng sync.Pool, preallocate slice/map

Giảm thời gian stop-the-world

Tránh pointer không cần thiết, dùng value types

Giám sát và tuning

Dùng pprof, GODEBUG, escape analysis


Last updated