Bài đọc gctrace nhắc tới con số goal — ngưỡng heap kích hoạt GC lần sau. Con số đó không phải cố định: nó do một biến duy nhất điều khiển, GOGC, và biến này là núm đánh đổi trực tiếp giữa CPU và bộ nhớ của toàn bộ chương trình Go. Hiểu nó là hiểu cách "điều tiết" GC. Bài này đo cùng một workload với các giá trị GOGC khác nhau và cho thấy đánh đổi rõ như ban ngày.
GC pacing: chạy theo tăng trưởng, không theo đồng hồ
Điểm mấu chốt: GC của Go không chạy theo lịch thời gian (mỗi X giây) mà theo mức tăng trưởng heap. Sau mỗi chu kỳ GC, runtime biết heap sống là bao nhiêu, rồi đặt ngưỡng cho lần sau theo công thức:
Ngưỡng GC lần sau = heap_sống × (1 + GOGC/100)
Với GOGC=100 (mặc định), ngưỡng = 2× heap sống — GC chạy khi heap tăng gấp đôi so với phần sống. Khi heap cấp phát chạm ngưỡng, GC chạy, dọn rác, tính lại heap sống, đặt ngưỡng mới. Đây gọi là GC pacing — điều tiết nhịp GC theo lượng rác sinh ra.

Hình 1: Công thức pacing. GOGC càng cao, heap được phình càng nhiều trước khi GC chạy — đổi bộ nhớ lấy ít lần GC hơn. Chỉnh bằng biến GOGC hoặc debug.SetGCPercent.
Đo thật: đánh đổi CPU và bộ nhớ
Chạy đúng một workload (cấp 64KB rồi bỏ liên tục, 3000 vòng, giữ 20 buffer sống) với các giá trị GOGC khác nhau, đo số lần GC (đại diện chi phí CPU) và đỉnh heap (bộ nhớ):

Hình 2: Từ GOGC=50 tới 400, số lần GC giảm từ 148 xuống 13 (đỡ CPU) nhưng đỉnh heap tăng từ 4 lên 15 MB. GOGC=off: 0 lần GC nhưng heap vọt lên 187 MB.
Con số vẽ ra đường đánh đổi hoàn hảo:
- GOGC=50: 148 lần GC, đỉnh heap 4 MB, STW 6,76 ms. GC chạy sớm (khi heap = 1,5× sống) → dày, tốn CPU, nhưng giữ bộ nhớ thấp.
- GOGC=100 (mặc định): 68 lần GC, 8 MB, 3,19 ms.
- GOGC=200: 33 lần GC, 10 MB, 1,06 ms.
- GOGC=400: 13 lần GC, đỉnh heap 15 MB, STW 0,57 ms. GC chạy muộn (5× sống) → thưa, đỡ CPU, nhưng tốn bộ nhớ.
- GOGC=off: 0 lần GC, heap vọt lên 187 MB. Tắt hẳn gom rác — giữ mọi thứ, bộ nhớ tăng không giới hạn.
Đường đánh đổi rất sạch: tăng GOGC gấp 8 lần (50→400) làm số lần GC giảm ~11 lần (148→13) nhưng đỉnh heap tăng ~4 lần (4→15 MB). Bạn đang mua ít CPU-cho-GC hơn bằng nhiều bộ nhớ hơn.
Ứng dụng thực tế
Tăng GOGC khi có RAM dư và GC ngốn CPU. Nếu dịch vụ của bạn có nhiều RAM trống và gctrace cho thấy %CPU-cho-GC cao, tăng GOGC (ví dụ 200-400) giảm số lần GC, trả CPU về cho công việc thật. Nhiều dịch vụ throughput cao chạy GOGC=200-400.
Giảm GOGC khi RAM eo hẹp. Trong container giới hạn bộ nhớ chặt, GOGC thấp (50-75) giữ heap nhỏ hơn, tránh chạm giới hạn — đổi lại GC chạy dày hơn. Nhưng cách hiện đại hơn là dùng GOMEMLIMIT (bài sau) thay vì hạ GOGC.
Chỉnh trong code với debug.SetGCPercent. Nếu cần đổi GOGC theo pha chương trình (ví dụ tắt GC trong lúc khởi tạo dữ liệu lớn rồi bật lại), debug.SetGCPercent(n) làm được lúc chạy. Trả về giá trị cũ để khôi phục.
Đánh đổi cần cân nhắc
GOGC=off cực kỳ nguy hiểm trên dịch vụ dài hạn. Đo cho thấy heap vọt lên 187 MB chỉ trong một workload ngắn. Tắt GC trên dịch vụ chạy lâu sẽ dẫn tới hết bộ nhớ (OOM) chắc chắn. Chỉ dùng GOGC=off cho công cụ chạy một lần, ngắn, biết trước lượng cấp phát (như trình biên dịch batch).
Không có con số GOGC "đúng" chung. Đánh đổi phụ thuộc hoàn toàn workload: tỉ lệ rác/sống, mẫu cấp phát, RAM khả dụng, yêu cầu độ trễ. Con số trong bài (4-15 MB) là của workload cụ thể này; workload của bạn khác. Phải đo trên chính chương trình và tải thật để chọn.
Đỉnh heap không chỉ do GOGC. Cấp phát đột biến (spike) có thể đẩy heap vượt ngưỡng goal trước khi GC kịp phản ứng, đặc biệt với GOGC cao. Nếu độ trễ và bộ nhớ đều quan trọng, GOGC một mình không đủ — cần kết hợp với GOMEMLIMIT để đặt trần cứng.
Ba ý mang về
- GC chạy theo tăng trưởng heap, không theo đồng hồ: GOGC đặt ngưỡng GC lần sau = heap_sống × (1 + GOGC/100), mặc định GOGC=100 nghĩa là GC khi heap tăng gấp đôi phần sống.
- GOGC là núm đánh đổi thẳng CPU ↔ bộ nhớ: đo thật, từ GOGC=50 tới 400, số lần GC giảm từ 148 xuống 13 (đỡ CPU) nhưng đỉnh heap tăng từ 4 lên 15 MB — tăng GOGC khi có RAM dư và GC ngốn CPU, giảm khi RAM eo hẹp.
- GOGC=off tắt hẳn GC, chỉ hợp công cụ ngắn hạn: đo thật heap vọt 187 MB không gom — trên dịch vụ dài hạn sẽ OOM; và không có con số GOGC đúng chung, phải đo trên workload thật.
Phần sau ta xét công cụ bổ trợ giải quyết đúng điểm yếu của GOGC — đặt trần bộ nhớ: Phần sau mổ xẻ GOMEMLIMIT — giới hạn bộ nhớ mềm, cách nó phối hợp với GOGC để vừa đỡ CPU vừa không tràn RAM container.