Nội dung bài viết
Đăng ký học lập trình C++
Tại STDIO bạn được dạy nền tảng lập trình tốt nhất.
Đăng ký học
Đối với class và struct thì việc cấp phát và tổ chức bộ nhớ có sự khác biệt với cách cấp phát đối với các biến thông thường như int, float, ... vấn đề này được gọi là Data Alignment. Trong bài viết này tôi giải thích lý do tồn tại khái niệm Data Alignment.

Giới thiệu

Vấn đề cấp phát và quản lý bộ nhớ mà hệ điều hành cung cấp đối với các kiểu dữ liệu tự định nghĩa có sự đặc biệt so với cấp phát với các kiểu dữ liệu cơ bản (char, short, int, ...). Trong bài viết này, tôi sẽ làm rõ tại sao hệ điều hành lại tạo ra cái gọi là Data Alignment.

Tiền đề bài viết

Trong thời gian theo học tại STDIO về lập trình C++ Nâng Cao, Kevin La đã khai sáng cho tôi nhiều kiến thức chuyên sâu và luôn khuyến khích tôi chia sẻ, qua đó tôi cũng hiểu được trách nhiệm của mình cho công việc sau này.

Nay tôi chọn Data Alignment để tạo thành bài viết đầu tiên để giải thích tại sao lại phải có Data Alignment và phát triển tiếp bài viết Struct Alignment Trong C++.

Đối tượng hướng đến

Bài viết hướng đến những lập trình viên có kiến thức vững chắc về cấp phát và tổ chức bộ nhớ máy tính, có mong muốn hiểu sâu thêm về hệ thống máy tính.

Memory access granualarity

Khi lập trình, chúng ta thường thấy bộ nhớ là một chuỗi các ô nhớ liên tiếp nhau, mỗi ô nhớ tương đương với 1 byte. 

Tuy nhiên, đối với hệ thống máy tính, việc đọc và ghi với từng ô nhớ như vậy tốn nhiều thời gian dẫn tới việc giảm hiệu suất. Vì vậy, tại mỗi thời điểm, processor truy cập bộ nhớ với 2-, 4-, 8-, 16- và thậm chí 32- byte tùy vào từng hệ thống. Kích thước khi một process truy cập bộ nhớ được gọi là bộ nhớ truy cập chi tiết (Memory access granularity).

Data Alignment

Ta đi xét các kết quả của các bộ nhớ truy cập chi tiết (Memory access granularity) khác nhau để có thể hiểu rõ cơ chế. Ở đây tôi sử dụng hệ thống 32 bit - x86, nên không gian mỗi lần register load dữ liệu sẽ là 4 bytes.

Single-byte memory access granularity

Với 1 byte, register sẽ chia làm 4 phần, hiện tại các processor không sử dụng theo cơ chế này, tuy nhiên tôi đưa ra để so sánh với cơ chế bên dưới.

Single-byte memory access

Khi đọc, ghi 4 byte từ địa chỉ 0x00 hay 0x01 đều không có sự khác biệt do truy cập ở kích thước là 1 byte.

Double-byte memory access granularity

Với 2 bytes, register được chia làm ít nhất là 2 phần. Với việc tăng gấp đôi bộ nhớ truy cập chi tiết, nghĩa là công việc giảm 1 nửa, ta thấy được lợi ích của mô hình này khi đọc ghi dữ liệu ở địa chỉ 0.

Double-byte memory access

Tuy nhiên khi đến địa chỉ 1, do không thuộc vào ranh giới truy cập (địa chỉ ô nhớ chia hết cho 2), nên processor phải làm thêm một số công việc để xử lý là truy cập thêm bộ nhớ, dẫn tới làm chậm lại các hoạt động. Dữ liệu ô nhớ không tương thích như vậy được gọi là "Unaligned memory access".

Quad-byte memory access granularity

Tương tự như Double-byte memory access granularity, nhưng ta hãy xét cách mà processor xử lý "Unaligned memory access".

Quad-byte memory access

Trường hợp này, muốn lấy được vùng nhớ data, processor sẽ phải lấy 4 byte phái trên và để ô nhớ dịch lên 1 byte và lấy 4 byte phái dưới và dịch lên trên 3 byte, sau đó ghép lại sẽ thu được vùng nhớ data.

Rất nhiều công việc phải thực hiện, như vậy làm giảm rất nhiều hiệu suất của hệ thống.

Vậy nên ta cần tổ chức thực hiện cơ chế nào đó để không còn Unaligned memory access thì hiệu suất của hệ thống sẽ tăng lên rất nhiều. Đó là lý do tại sao ta cần Data Alignment.

Thử nghiệm

Để so sánh hiệu suất và thời gian xử lý, cần bài thử nghiệm so sánh giữa các mô hình: Tôi xin sử dụng kết quả thực nghiệm của Jonathan Rentzsch, bài thử nghiệm được thực hiện trên hệ thống Macbook Pro, quad-core 2.8 GHz Intel Core i7 64-bit processor, 16GB RAM.

qyUgm

Bạn có thể xem chi tiết thông số trong Table Result

Struct Alignment

Để hiểu rõ hơn tôi sẽ xét kết quả ví dụ của bài viết trước:

struct Student
{
     char id;
     int age;
     double gpa;
};

Kết quả ta thu được là:

Alignment-result

Kích thước của struct là 16 chứ không phải 13, và vùng nhớ cấp phát cho struct luôn luôn là liên tục trong bộ nhớ, địa chỉ của id, age, gpa lần lượt là 0x04, 0x08, 0xC.

Tại sao phải "padding", để tạo nên 1 vùng nhớ "thừa"?

Tuy bạn thấy vùng nhớ đó thừa, tuy nhiên nó làm hạn chế rất nhiều process rơi vào trường hợp địa chỉ phần tử nằm ô nhớ không chia hết cho 2.

Liên hệ với trường hợp Quad-byte memory access granularity, nếu như vùng nhớ data tương ứng với biến int mà lại nằm tại ô nhớ 0x05 thì rõ ràng process sẽ phải làm thêm nhưng việc như trên. Đó là lý do tại sao phải có Data Alignment.

Tham khảo

Bài viết có sử dụng một số kết quả từ các nguồn, rất cảm ơn vì đó là nguồn tham khảo quan trọng cho bài viết này:

  • Struct Alignment Trong C++ của tác giả Nguyễn Nghĩa.
  • Kết quả thử nghiệm các mô hình của Jonathan Rentzsch.
  • Bạn có thể tham khảo mã nguồn sử dụng để thử nghiệm tại đây.
  • Cám ơn anh Kevin La và STDIO đã dạy tôi cách tư duy.
THẢO LUẬN