Memory Alignment là gì?
Dẫn nhập
Ta có bao giờ để ý tới size của class và struct chưa? Giả dụ ta có struct S như sau, bạn đọc đoán xem size của struct S là bao nhiêu?
type S struct {
a int8
b int64
c int32
}Nếu ta dùng theo quy chiếu thông thường về size của golang, ta có thể dễ dàng tính được size của S là 1 + 8 + 4 = 13 bytes. Điều đó không hề sai về mặt lý thuyết, chỉ là compiler của các ngôn ngữ không làm thế mà thôi. Thật tế, nếu dùng lệnh unsafe.Sizeof(S{}) thì kết quả sẽ là 24 bytes. Tại sao lại có kết quả lạ thường như thế?
Bảng 1: Kích thước của các kiểu dữ liệu trong Go.
| Type | Size (bytes) |
|---|---|
| byte, uint8, int8 | 1 |
| uint16, int16 | 2 |
| uint32, int32, float32 | 4 |
| uint64, int64, float64, complex64 | 8 |
| complex128 | 16 |
Memory Alignment
Trong máy tính kiến trúc 32 bit thì mỗi cycle của CPU khi load lên register thông thường sẽ là 4 bytes.

Giả sử ta lại có kiểu dữ liệu int32 thì sẽ chiếm 2 ô nhớ trong RAM. Và máy tính vẫn hoạt động tốt nếu biến có kiểu int32 nằm trong đoạn 0x0 -> 0x2.

Nhưng câu chuyện phức tạp sẽ xảy ra nếu như biến bắt đầu nằm ở vị trí 0x3. Khi đó, để load biến thì CPU tốn 2 cycle để load dữ liệu, cắt, ghép lại để được biến kiểu int32 đó.

Do đó, các ngôn ngữ sẽ tự thêm các bytes trống để làm sao khi đọc tốn số lượng cycle ít nhất có thể, và được gọi là padding.
Alignment guarantee
Nói đơn giản, alignment guarantee cho biết: một giá trị của type này phải bắt đầu ở địa chỉ bộ nhớ nào.
unsafe.Alignof(...).Với kiểu dữ liệu cơ bản như int, byte, float,... thì alignment guarantee thường phụ thuộc vào kiến trúc máy tính như 64 bit hoặc 32 bit. Đa số là trùng với size của kiểu dữ liệu.
Bảng 2: Size và alignment guarantee của các kiểu dữ liệu trong Go trên kiến trúc 64 bit.
| Kiểu dữ liệu (Type) | Kích thước (Size - Bytes) | Căn chỉnh (Alignment - Bytes) |
|---|---|---|
| bool, uint8, int8, byte | 1 | 1 |
| uint16, int16 | 2 | 2 |
| uint32, int32, float32 | 4 | 4 |
| string | 16 (Header) | 8 |
| slice | 24 (Header) | 8 |
| interface | 16 (Header) | 8 |
| pointer (con trỏ) | 8 | 8 |
| struct{} (struct rỗng) | 0 | 1 |
Go cũng có đưa ra một số đặc tả như sau:
- For a variable
xof any type:unsafe.Alignof(x)is at least 1. - For a variable
xof struct type:unsafe.Alignof(x)is the largest of all the valuesunsafe.Alignof(x.f)for each fieldfofx, but at least 1. - For a variable
xof array type:unsafe.Alignof(x)is the same as the alignment of a variable of the array's element type.
Thực nghiệm
Giả sử ta có kiểu dữ liệu S1 như sau:
type S1 struct {
a int8
// 7 bytes sẽ được padding tại đây
b int64
c int16
// 6 bytes được padding tại đây
}S1 sẽ có size là 24:
- Đầu tiên, struct có alignment guarantee là alignment guarantee của field lớn nhất: struct
S1sẽ có alignment guarantee củaint64là 8 bytes. Do đó, khoảng cách của fieldavàblà 7 bytes. - Size của struct phải là bội số của alignment guarantee, ở đây là 8. Do đó, biến
ccuối cùng sẽ được thêm 6 bytes cuối.
Tuy nhiên, vẫn là struct đó, nhưng ta khéo tổ chức dữ liệu hơn như sau:
type S2 struct {
a int8
// 1 bytes được padding tại đây
c int16
// 4 bytes được padding tại đây
b int64
}Bây giờ, size chỉ còn là 16 thôi và tiết kiệm được 8 bytes.
Kết luận
Việc ta không để ý tới memory alignment có thể dẫn đến một số vấn đề sau:
- CPU có khả năng tốn nhiều ops hơn để đọc dữ liệu. Go compiler/runtime đã đảm bảo alignment bằng cách chèn padding và cấp phát ở địa chỉ phù hợp, nhưng không tự đổi thứ tự field.
- Application chạy tốn RAM hơn. Vì phải padding nhiều hơn để có được vị trí đẹp.
- Ít phần tử fit vào cache line hơn.
Bình luận