Cách truy cập RAM hiệu quả
Các phần mềm hiện nay thường ít chú ý đến hiệu năng vì phần cứng trở nên rất mạnh và quan trọng hơn nữa là giá thành phần cứng rẻ đi rất nhiều. Tuy nhiên nếu lập trình viên hiểu biết cấu trúc và cách hoạt động của CPU sẽ tránh phạm phải các lỗi cơ bản và dễ dàng nâng cao hiệu năng của hệ thống.
Cache line
Trước khi CPU có thể truy cập đến các lệnh (dưới dạng mã máy) và dữ liệu, chúng phải được nạp vào các vùng nhớ đệm (L1-L2-L3). Các vùng nhớ đệm này không được đọc/ghi theo từng byte đơn lẻ mà theo nhóm các byte liền kề (batch). Nhóm các byte này được gọi là cache line. Độ lớn của cache line tùy thuộc loại CPU, nhưng phần lớn là 64 byte. Việc đọc/ghi theo batch giúp tăng tốc độ truyền dữ liệu giữa CPU cache và bộ nhớ, nhưng nếu lập trình viên không am hiểu có thể ảnh hưởng rất nhiều tới hiệu năng của hệ thống.
Cách load dữ liệu vào bộ nhớ đệm[4]
Cache miss[1]
Cache miss là khi CPU không có sẵn dữ liệu trong cache để đọc/ghi dẫn tới việc truy xuất dữ liệu từ bộ nhớ khiến cho tốc độ thực thi của CPU bị giảm. Có 3 loại cache miss là instruction read miss, data read miss, và data write miss.
Ảnh hưởng lớn nhất tới hiệu năng là Instruction read miss, xảy ra khi CPU phải chờ các lệnh tiếp theo được nạp vào cache. Data read miss ít ảnh hưởng tới hiệu năng hơn vì CPU có thể tiếp tục thực thi trong khi chờ dữ liệu được nạp vào cache. Ít ảnh hưởng nhất là data write miss, dữ liệu cần ghi sẽ được đưa vào hàng đợi để ghi vào bộ nhớ chính và CPU vẫn có thể tiếp tục thực thi ngay cả khi hàng đợi đã đầy.
Failure sharing
Khi 2 hay nhiều CPU (hoặc thread) cùng sử dụng dữ liệu trong một cache line, cho dù chúng không sử dụng chung một biến dữ liệu, mọi cập nhật dữ liệu của CPU này đều khiến cho tất cả CPU khác phải load lại cache line đó vào cache. Như hình vẽ dưới đây, 2 CPU truy cập vào 2 biến X và Y khác nhau nhưng cùng một cache line.
Hai CPU có thể sử dụng chung một cache line[2]
Failure sharing khiến cho CPU hệ thống phải liên tục nạp dữ liệu không cần thiết vào cache, khiến cho hiệu năng của hệ thống bị suy giảm. Càng nhiều thread tham gia vào quá trình tính toán thì vấn đề Failure sharing càng nghiêm trọng. Nói cách một cách khác, hiệu năng của hệ thống không tăng tuyến tính theo số lượng thread. Giải pháp cho vấn đề này là đặt các dữ liệu của các CPU. Đối với C# ta có thể khai báo như sau:
[StructureLayout(LayoutKind.Explicit)]
struct Data
{
[FieldOffset(0)]
public int x;
[FieldOffset(64)]
public int y;
}
Nếu giảm offset của biến y sẽ gây Failure sharing
Với Java có thể khai báo với annotation @Contended
public class PaddedObject{
@Contended
public volatile long valueA;
public volatile long valueB;
}
Chú ý chỉ sử dụng với Java 8
Duyệt mảng 2 chiều theo dòng
Mảng 2 chiều được lưu trữ trong bộ nhớ chính giống như mảng một chiều, nhưng ở trên nhiều vùng nhớ cạnh nhau. Vì vậy duyệt mảng 2 chiều theo dòng sẽ giảm được data read miss
List<Interger> result = new ArrayList<>(size);
for(int j = 0; j < 1024; j++)
for(int i = 0; i < 1024; i++)
result.add (state.testData [i][j]);
Duyệt mảng theo cột (nguồn ^3)
List<Interger> result = new ArrayList<>(size);
for(int i = 0; i < 1024; i++)
for(int j = 0; j < 1024; j++)
result.add (state.testData [i][j]);
}
Duyệt mảng theo dòng[3], tốc độ cao hơn đến 2 lần
Kết luận
Trên đây là một số trường hợp ít gặp với các phần mềm quản lý thông thường, nhưng thường xuyên xảy ra đối với các hệ thống tính toán số liệu cần hiệu năng cao hoặc các hệ thống đòi hỏi độ trễ (latency) thấp. Nắm rõ cơ chế hoạt động của phần cứng sẽ giúp lập trình viên tận dụng tối đa phần cứng.
=========================