Go chuyên sâu: Runtime, Hiệu năng và Hệ thống

Sê-ri 132 bài Go nâng cao — runtime, scheduler, GC, bộ nhớ, compiler, concurrency nâng cao, kiến trúc, mạng phân tán và vận hành. Mỗi bài đo THẬT bằng Go 1.23.

18/132 phần đã đăng Lập trình Go
1 Mô hình GMP trong Go: 100.000 goroutine chạy trên chỉ 5 OS thread Goroutine chạy ở đâu? Go dùng mô hình GMP — G (goroutine) ghép lên M (OS thread) qua P (processor). Bài này đo thật: 100.001 goroutine chạy trên chỉ 5 thread qua 10 processor, và vì sao đó là lý do Go tạo goroutine gần như miễn phí. Go 1.23. 22/09/2026 · 6 phút đọc 2 Work-stealing trong Go: vì sao 40 goroutine tạo từ một chỗ vẫn chạy hết 10 core Bộ lập lịch Go dùng work-stealing: một P hết việc sẽ đánh cắp một nửa hàng đợi của P khác. Bài này đo thật 40 việc CPU tạo từ main chạy nhanh 6,5 lần khi bật 10 core, và schedtrace cho thấy idleprocs=0 — mọi core đều bận. Go 1.23. 22/09/2026 · 6 phút đọc 3 Đọc GODEBUG=schedtrace trong Go: giải mã trạng thái bộ lập lịch không cần thư viện schedtrace là công cụ số một để nhìn vào bộ lập lịch Go. Bài này giải mã từng trường của một dòng SCHED thật, và theo dõi vòng đời một run qua bốn snapshot — từ lúc goroutine dồn vào hàng đợi cục bộ tới khi mọi core bận. Kèm cách đọc để chẩn đoán nút thắt. Go 1.23. 22/09/2026 · 6 phút đọc 4 Async preemption trong Go: vì sao một vòng lặp for{} từng treo cả chương trình Trước Go 1.14, một goroutine chạy vòng lặp chặt không gọi hàm có thể độc chiếm processor mãi mãi. Bài này đo thật: cùng một for{} vô hạn, bật async preemption thì main quay lại được (exit 0), tắt thì treo tới timeout (exit 124). Kèm cơ chế cắt bằng tín hiệu. Go 1.23. 22/09/2026 · 6 phút đọc 5 Netpoller trong Go: vì sao 5000 kết nối chỉ tốn 16 OS thread I/O mạng chặn goroutine mà không chặn OS thread — nhờ netpoller tích hợp epoll/kqueue vào scheduler. Bài này đo thật: 5000 goroutine chờ network Read chỉ dùng 16 thread, trong khi 200 goroutine chặn trong syscall thật ngốn 203 thread. Đây là cách Go giải bài C10k. Go 1.23. 22/09/2026 · 6 phút đọc 6 Sysmon: thread giám sát nền của Go, và cách nó cứu goroutine khỏi chết đói vì syscall Sysmon là thread nền không gắn P, chạy suốt vòng đời chương trình Go. Đo thật: GOMAXPROCS=1, một goroutine kẹt syscall 320ms mà goroutine khác vẫn đếm được 103 triệu lần — vì sysmon giành lại P. Và 40 syscall blocking đẻ ra 43 OS thread. Go 1.23. 22/09/2026 · 7 phút đọc 7 Vì sao goroutine chỉ tốn 2KB: stack tăng trưởng động trong Go, đo tận bytes Goroutine sinh ra với stack 2KB chứ không phải 8MB như OS thread. Đo thật: 100.000 goroutine chỉ 2051 bytes stack mỗi cái; khi đệ quy sâu, runtime cấp stack gấp đôi (tỉ lệ x2.00 chính xác) và copy toàn bộ khung — địa chỉ biến cục bộ nhảy vùng. Go 1.23. 22/09/2026 · 7 phút đọc 8 Chuyển ngữ cảnh goroutine rẻ hơn thread 130 lần: đo tận nanosecond Vì sao spawn hàng nghìn goroutine chạy mượt còn hàng nghìn OS thread thì nghẹt? Đo thật: ping-pong qua channel tốn 164 ns/vòng và 0 cấp phát, còn ping-pong qua pipe giữa hai OS thread tốn 21465 ns/vòng — đắt hơn 130 lần. Vì một cái ở user-space, cái kia qua kernel. Go 1.23. 22/09/2026 · 7 phút đọc 9 GOMAXPROCS trong container: vì sao Go hiểu taskset nhưng bỏ qua --cpus, và cách sửa Go đặt GOMAXPROCS theo số CPU nó nhìn thấy — nhưng trong Kubernetes, giới hạn CPU thường là CFS quota mà Go 1.23 không đọc. Đo thật: Go nhận biết affinity (taskset thành NumCPU 2) nhưng khi GOMAXPROCS thừa so với CPU thực, chuyển ngữ cảnh cưỡng bức tăng gấp 4 lần. Go 1.23. 22/09/2026 · 6 phút đọc 10 Hàng đợi chạy cục bộ và toàn cục trong Go: vì sao mỗi P có queue riêng 256 slot Bộ lập lịch Go không dùng một hàng đợi chung. Mỗi P giữ hàng đợi cục bộ lock-free tối đa 256 goroutine, cộng một hàng đợi toàn cục có khoá cho phần tràn. Đo thật bằng schedtrace: local queue không bao giờ vượt 256, global phình tới hàng vạn nhưng không bị bỏ đói. Go 1.23. 22/09/2026 · 7 phút đọc 11 Escape analysis trong Go: đọc -gcflags=-m để biết biến ở stack hay heap Trình biên dịch Go tự quyết biến nào ở lại stack (rẻ) và biến nào thoát lên heap (do GC quản). Đo thật: cùng logic, slice cỡ hằng số 0 cấp phát 0.23 ns, slice cỡ biến 128 byte 27.94 ns — chậm 120 lần. Cách đọc output -m và vì sao nó quan trọng. Go 1.23. 22/09/2026 · 7 phút đọc 12 Vì sao con trỏ thoát ra heap trong Go: bốn thủ phạm và cách đọc leaking param Con trỏ không tự làm biến lên heap — nó chỉ buộc thoát khi biến sống lâu hơn hàm. Đo thật bốn mẫu kinh điển bằng gcflags=-m: trả con trỏ, lưu toàn cục, đóng gói interface, và leaking param. Con trỏ dùng nội bộ vẫn ở stack 0 cấp phát. Go 1.23. 22/09/2026 · 7 phút đọc 13 Allocator ba tầng của Go: mcache, mcentral, mheap và vì sao cấp phát nhỏ không cần khoá Go cấp phát bộ nhớ qua ba tầng như tcmalloc: mcache mỗi P không khoá, mcentral có khoá, mheap khoá heap. Đo thật: cấp đối tượng nhỏ song song còn nhanh hơn một luồng (mcache per-P), cấp lớn song song chậm gấp 3 vì tranh khoá mheap. Và tiny allocator gộp đối tượng nhỏ. Go 1.23. 22/09/2026 · 6 phút đọc 14 Size classes và span trong Go: vì sao xin 33 byte lại tốn 48, và phí phạm nội bộ Go làm tròn mọi cấp phát lên một trong ~68 lớp kích thước cố định. Đo thật: xin 9 byte tốn 16 (phí 44%), xin 33 tốn 48 (phí 31%), nhưng object khớp lớp (16, 32, 48) thì 0 phí. Cách span tổ chức bộ nhớ và cách thiết kế struct khớp lớp. Go 1.23. 22/09/2026 · 6 phút đọc 15 Tiny allocator của Go: gộp object nhỏ để một byte chỉ tốn một byte Object tí hon rất phổ biến nhưng cấp mỗi cái một slot 8-16 byte quá phí. Tiny allocator gộp object dưới 16 byte không con trỏ vào chung khối 16B. Đo thật: object 1-byte nén xuống đúng 1.0 B mỗi cái (thắng 8 lần), nhưng cùng cỡ mà có con trỏ thì không được gộp. Go 1.23. 22/09/2026 · 6 phút đọc 16 Đo cấp phát bằng -benchmem: hai cái bẫy khiến benchmark Go nói dối 0 alloc B/op và allocs/op là công cụ tối ưu bộ nhớ Go, nhưng dễ nói dối. Đo thật: cùng một hàm cấp 1024 byte, benchmark báo 0 alloc khi kết quả bị vứt (compiler xoá code), và 1 alloc khi gán vào sink. Cách viết benchmark cấp phát đáng tin và testing.AllocsPerRun. Go 1.23. 22/09/2026 · 6 phút đọc 17 Tái dùng buffer trong Go: mẫu [:0] và prealloc cắt allocation từ 10 xuống 0 Hai kỹ thuật giảm allocation không cần thư viện. Đo thật: tái dùng buffer với buf[:0] cắt 10 cấp phát xuống 0 (nhanh 3.3 lần); prealloc đúng capacity cắt 12 cấp phát xuống 1 (nhanh 2.5 lần). Cơ chế, khi nào dùng, và các bẫy chia sẻ backing array. Go 1.23. 22/09/2026 · 6 phút đọc 18 sync.Pool trong Go: cơ chế, bẫy làm nó chậm hơn, và vì sao GC dọn sạch nó sync.Pool tái dùng object giữa các goroutine, nhưng không phải lúc nào cũng nhanh hơn. Đo thật: pool []byte khi buffer không thoát heap còn CHẬM hơn (compiler để stack + boxing); pool *bytes.Buffer khi object thật sự cấp phát thì nhanh 28%. Và GC dọn sạch pool sau hai chu kỳ. Go 1.23. 22/09/2026 · 6 phút đọc

Còn 114 phần nữa sẽ lần lượt được đăng.