Hai struct có đúng cùng các field — một int64 và hai bool — nhưng một cái tốn 24 byte, cái kia chỉ 16 byte. Khác biệt duy nhất là thứ tự khai báo field. Đây không phải chuyện lý thuyết: với cấu trúc dữ liệu cấp phát hàng triệu lần, thứ tự field dở có thể phí hàng chục MB. Bài này đo chính xác cơ chế đằng sau — alignment và padding — và cách sắp field để struct gọn nhất mà không đổi một dòng logic.
Alignment: vì sao có byte đệm
Mỗi kiểu dữ liệu có một yêu cầu căn lề (alignment): địa chỉ bộ nhớ của nó phải chia hết cho một số nhất định. Trên máy 64-bit, int64 căn lề 8 (phải nằm ở địa chỉ chia hết cho 8), int32 căn lề 4, bool/byte căn lề 1. Lý do là phần cứng: CPU đọc dữ liệu căn lề trong một thao tác, còn dữ liệu lệch lề có thể cần nhiều thao tác hoặc bị cấm.
Vì field trong struct được đặt theo đúng thứ tự khai báo, trình biên dịch phải chèn byte đệm (padding) giữa các field để mỗi field rơi vào địa chỉ căn lề đúng. Thứ tự field dở tạo ra nhiều đệm phí.

Hình 1: Cùng ba field. Xau (bool-int64-bool) xen kẽ nên int64 cần 7 byte đệm trước nó → 24 byte. Tot (int64-bool-bool) gom bool lại → 16 byte. Sơ đồ byte cho thấy đệm phí.
Đo thật: cùng field, khác thứ tự, khác kích thước
Dùng unsafe.Sizeof, unsafe.Alignof, và unsafe.Offsetof để soi bố cục thực:
type Xau struct { a bool; b int64; c bool } // xen kẽ
type Tot struct { b int64; a bool; c bool } // lớn trước
fmt.Println(unsafe.Sizeof(Xau{}), unsafe.Sizeof(Tot{}))
fmt.Println(unsafe.Offsetof(Xau{}.b)) // vị trí field b

Hình 2: Xau = 24 byte (offset a=0, b=8, c=16). Tot = 16 byte (offset b=0, a=8, c=9). Chênh 8 byte, 33% chỉ nhờ đổi thứ tự — thành 228 MB so với 152 MB ở 10 triệu struct.
Đọc offset là thấy rõ chuyện gì xảy ra:
Xau(24 byte):aở offset 0 (1 byte), nhưngb(int64) cần offset chia hết 8 nên phải nhảy tới offset 8 — 7 byte đệm phí ở giữa.cở offset 16, rồi cả struct phải căn lề 8 nên thêm 7 byte đệm cuối. Tổng đệm phí: 14 byte trên 24.Tot(16 byte):bở offset 0 (int64 vừa khít),aở offset 8,cở offset 9 (hai bool gom liền nhau), rồi 6 byte đệm cuối để đạt 16. Đệm ít hơn hẳn.
Cùng ba field, cùng dữ liệu, chỉ khác thứ tự khai báo — mà Xau tốn nhiều hơn 33%. Ở 10 triệu struct, đó là 228 MB so với 152 MB, chênh 76 MB.
Quy tắc sắp field
Nguyên tắc đơn giản: sắp field từ lớn tới nhỏ. Đặt các field 8-byte (int64, con trỏ, slice header, map, chan) trước, rồi 4-byte (int32, float32), rồi 2-byte, rồi 1-byte (bool, byte) cuối. Cách này gom các field nhỏ vào cuối, để chúng lấp đầy nhau thay vì mỗi cái tạo một khoảng đệm. Alignof của cả struct bằng alignment của field lớn nhất.
Ứng dụng thực tế
Tối ưu bộ nhớ miễn phí cho struct cấp phát nhiều. Nếu bạn có một struct dùng làm phần tử của slice/map lớn, node của cây, hay bản ghi trong hàng triệu, sắp lại field theo thứ tự lớn-tới-nhỏ cắt bộ nhớ ngay mà không đổi logic. Đây là một trong ít tối ưu "chỉ có lợi, không có hại".
Ít bộ nhớ = ít áp lực GC và tốt cache. Struct gọn hơn không chỉ tiết kiệm RAM: nó giảm lượng dữ liệu GC phải quét, và nhiều struct hơn khớp vào một cache line (64 byte) — cải thiện locality khi duyệt mảng struct. Lợi ích lan tỏa hơn con số byte thuần.
Dùng công cụ để phát hiện. go vet có kiểm fieldalignment (qua golang.org/x/tools), và lệnh fieldalignment -fix tự sắp lại field cho struct của bạn. Không cần soi tay từng struct — chạy công cụ trên các struct nóng.
Đánh đổi cần cân nhắc
Thứ tự field theo logic vs theo kích thước. Sắp field theo kích thước đôi khi làm struct khó đọc hơn (các field liên quan bị tách ra). Với struct ít cấp phát, tính dễ đọc quan trọng hơn vài byte — giữ thứ tự logic. Chỉ tối ưu alignment cho struct thật sự nóng về bộ nhớ.
Đừng chèn padding thủ công trừ khi có lý do. Đôi khi bạn cố ý thêm padding (ví dụ chống false sharing — bài sau). Nhưng thêm field _ [n]byte bừa bãi làm struct phình vô ích. Chỉ chèn padding khi đo được lợi ích cụ thể.
Kích thước struct còn phụ thuộc kiến trúc. Trên 32-bit, alignment và kích thước con trỏ khác (con trỏ 4 byte), nên bố cục struct đổi. unsafe.Sizeof cho kết quả đúng của kiến trúc đang biên dịch; đừng hardcode con số kích thước struct vào logic. Bài này đo trên arm64 64-bit.
Ba ý mang về
- Thứ tự field đổi kích thước struct vì trình biên dịch chèn byte đệm để căn lề: đo thật, cùng ba field (int64 + 2 bool) nhưng
bool-int64-booltốn 24 byte (14 byte đệm phí) so vớiint64-bool-booltốn 16 byte — chênh 33%. - Sắp field từ lớn tới nhỏ để giảm đệm: đặt field 8-byte trước, gom field 1-byte cuối để chúng lấp đầy nhau — dùng
unsafe.Sizeof/Offsetofđể soi, vàgo vet fieldalignment -fixđể tự sửa. - Tối ưu miễn phí ở quy mô lớn: 10 triệu struct chênh 76 MB chỉ nhờ đổi thứ tự khai báo — vừa tiết kiệm RAM, vừa giảm việc cho GC, vừa tốt cache; nhưng chỉ đáng làm cho struct nóng, struct thường thì ưu tiên thứ tự dễ đọc.
Phần sau ta xét mặt trái của việc nhồi dữ liệu gần nhau: Phần sau mổ xẻ false sharing và cache line — vì sao hai biến gần nhau bị nhiều lõi tranh chấp có thể làm chậm chương trình song song, và khi nào cần thêm padding để tách chúng.