Vì sao có RAM rồi CPU vẫn cần cache?
Tại sao không làm một loại bộ nhớ vừa nhanh như cache, vừa lớn như RAM, vừa rẻ như SSD?
Ở bài trước, CPU phải đi một chặng khá dài mỗi lần lấy dữ liệu từ RAM. Vì vậy nó giữ bản sao những gì vừa lấy trong cache, vùng nhớ nhỏ nằm ngay trên chip CPU.
Nhưng cache cũng chỉ là bộ nhớ. Nếu nó nhanh hơn RAM, sao không làm luôn:
16 GB cache
rồi bỏ RAM đi? Hay tốt hơn nữa: làm một loại bộ nhớ vừa nhanh như cache, vừa lớn như RAM, vừa rẻ như SSD?
Vấn đề là ba thứ đó không đi cùng nhau.
Nhanh, lớn, rẻ — chỉ chọn được vài thứ
Ba loại bộ nhớ đã gặp trong khoá có ba cách đánh đổi khác nhau.
SRAM làm nên cache CPU. Một bit thường cần khoảng sáu transistor; mạch tự giữ trạng thái 0 hoặc 1 nên đọc là thấy ngay mức điện. Đổi lại, sáu transistor cho một bit chiếm khá nhiều diện tích silicon. Muốn hàng chục gigabyte SRAM thì con chip sẽ cực lớn và cực đắt.
DRAM làm nên RAM chính. Một bit chỉ cần một transistor và một tụ điện, nên nhét được rất nhiều bit vào diện tích nhỏ. Đổi lại, nó phải mở cả hàng để đọc, phải khuếch đại một lượng điện tích rất nhỏ, và phải refresh liên tục. Rẻ hơn SRAM nhiều, nhưng CPU chờ lâu hơn.
Flash làm nên SSD. Nó giữ được dữ liệu cả khi mất điện và lưu rất nhiều bit với giá thấp. Nhưng để CPU chờ SSD mỗi lần cần một biến thì quá chậm.
Còn một giới hạn nữa, loại ô nhớ nào cũng gặp: khoảng cách. Tín hiệu điện cần thời gian để đi trên dây, mà một vùng nhớ càng lớn càng khó đặt trọn vẹn sát mạch tính toán. Phần lớn của nó buộc phải nằm xa hơn, qua nhiều dây và nhiều mạch hơn.
Bộ nhớ rất nhanh phải nhỏ và nằm gần CPU. Bộ nhớ rất lớn phải chấp nhận nằm xa hơn và chậm hơn.
Không có một tầng hoàn hảo, máy tính dùng nhiều tầng.
Bộ nhớ được xếp thành một cái tháp
Càng lên trên, bộ nhớ càng nhỏ và nhanh. Càng xuống dưới, càng lớn và rẻ.
| Tầng | Làm bằng gì |
|---|---|
| Thanh ghi (register) | mạch tự giữ, rất ít ô |
| L1 | SRAM, nhỏ nhất |
| L2 | SRAM, lớn hơn L1 |
| L3 | SRAM, lớn hơn nữa, thường dùng chung cho nhiều nhân |
| RAM | DRAM |
| SSD | flash, giữ được khi mất điện |
L1, L2 và L3 đều là cache. Chúng cũng được chia tầng vì đúng lý do vừa nói:
L1
nhỏ nhất
nhanh nhất
gần nhân nhất
↓
L2
↓
L3
lớn hơn
xa hơn
chậm hơn
↓
RAM
CPU luôn hỏi tầng gần nhất trước. Tìm thấy ở L1 thì không cần hỏi L2, thấy ở L2 thì không cần hỏi L3. Chỉ khi mọi tầng cache đều không có bản sao — cache miss — yêu cầu mới đi tới RAM.
Thử xem một lần đọc diễn ra thế nào. Chọn Chỉ có trong RAM, bấm CPU cần dữ liệu X, rồi bấm Chạy lại cùng dữ liệu.
Dữ liệu X đang nằm ở đâu?
- Thanh ghi trong CPU
- Cache L1
- Cache L2
- Cache L3
- RAM ngoài chip CPU
- SSD lưu lâu dài
CPU hỏi tầng gần nhất trước. Hụt (miss) thì hỏi tầng kế tiếp, có (hit) thì dừng.
Lần đầu CPU phải hỏi lần lượt từng tầng rồi mới xuống tới RAM. Lấy về xong, một bản sao của X được giữ lại trong cache, nên lần sau CPU tìm thấy nó ngay gần mình.
Nhưng điều đó đặt ra một câu hỏi khác. Cache nhỏ hơn RAM rất nhiều. Làm sao nó biết nên giữ đúng thứ CPU sắp cần?
Chương trình không đọc bộ nhớ một cách ngẫu nhiên
Nếu chương trình đọc rải đều khắp 16 GB thì cache gần như vô dụng: thứ cần tiếp theo hầu như chẳng bao giờ có sẵn. Nhưng chương trình thật không đọc như vậy.
Hãy tưởng tượng một mảng:
int numbers[] = {10, 20, 30, 40, 50};
và một vòng lặp cộng dồn:
for (...) {
sum += numbers[i];
}
CPU vừa dùng sum, một lát sau lại dùng sum. CPU vừa đọc numbers[0], ngay sau đó đọc numbers[1], rồi numbers[2].
Đây là một đặc điểm rất quan trọng của chương trình: tính cục bộ (locality). Có hai kiểu.
Thứ vừa dùng thường được dùng lại — cục bộ theo thời gian:
sum
↓
dùng
↓
dùng lại
↓
dùng lại
Giữ sum trong cache rất có ích.
Thứ nằm gần nhau thường được dùng gần nhau — cục bộ theo không gian:
a[0] → a[1] → a[2] → a[3]
Đó cũng là lý do bài trước CPU không chỉ mang về đúng vài byte nó cần, mà mang về cả một cache line, chẳng hạn 64 byte:
CPU cần a[0]
↓
[██][██][██][██][██][██][██][██]
↑
a[0]
nhưng cả vùng được đưa vào cache
Khi CPU đọc a[1], dữ liệu đã nằm sẵn ở đó.
Cache hiệu quả vì chương trình thường dùng lại thứ vừa dùng và thứ nằm ngay cạnh.
Vì vậy cache nhỏ vẫn có ích
Nhìn lại cả hệ thống:
CPU cần dữ liệu
↓
L1
↓
L2
↓
L3
↓
RAM
↓
SSD
Mỗi tầng cố che đi độ chậm của tầng bên dưới. Thanh ghi che cache, cache che RAM, RAM che SSD.
Tầng nhanh không cần lớn bằng tầng dưới. Nó chỉ cần chứa đúng phần dữ liệu đang được dùng nhiều nhất lúc này. Đó là phân cấp bộ nhớ (memory hierarchy).
Cache giúp CPU tránh rất nhiều chuyến đi tới RAM, nhưng nó không giữ được mọi thứ. Chương trình chuyển sang dữ liệu mới, cache không có bản sao, và CPU lại phải xuống RAM chờ.
Trong khi đó, vỏ hộp thanh RAM lại in một con số rất lớn:
DDR5-6000
mà phần mềm xem phần cứng lại hiện 3000 MHz.
Con số nào mới là tốc độ thật, và vì sao RAM nhanh như vậy mà CPU vẫn phải chờ?