Như @dlev đã đề cập, điều này là do locality of reference và phải làm thế nào với phần cứng vật lý trong máy tính hoạt động.
Bên trong máy tính, có nhiều loại bộ nhớ khác nhau. Thông thường, chỉ một số vị trí bộ nhớ nhất định (thanh ghi) có thể thực hiện các hoạt động thực tế trên chúng; phần còn lại của thời gian, nếu bạn đang thực hiện các hoạt động trên dữ liệu, bạn phải tải nó từ bộ nhớ vào một thanh ghi, thực hiện một số tính toán, sau đó ghi nó trở lại.
Bộ nhớ chính (RAM) nhiều, chậm hơn nhiều so với thanh ghi, thường là từ một trăm đến hàng nghìn. Do đó, việc đọc từ bộ nhớ nên tránh nếu có thể. Để giải quyết vấn đề này, hầu hết các máy tính thường có các vùng nhớ đặc biệt được gọi là caches. Công việc của bộ nhớ cache là giữ dữ liệu gần đây đã được truy cập từ bộ nhớ sao cho nếu cùng một vùng bộ nhớ đó được truy cập lần nữa, giá trị có thể được lấy từ bộ nhớ cache (nhanh) thay vì từ bộ nhớ chính (chậm). Thông thường, cache được thiết kế sao cho nếu một giá trị được đọc từ bộ nhớ, giá trị đó, cộng với một bó toàn bộ các giá trị liền kề, được đưa vào bộ nhớ cache. Bằng cách đó, nếu bạn lặp qua một mảng, sau đó sau khi đọc giá trị đầu tiên, phần còn lại của các giá trị từ mảng sẽ được đặt trong bộ đệm và có thể được truy cập hiệu quả hơn.
Lý do mã của bạn chậm hơn mức cần thiết là mã không truy cập vào phần tử mảng tuần tự. Trong C, mảng 2D được trình bày dưới row-major order, có nghĩa là bộ nhớ được bố trí như
A[0][0] A[0][4] A[0][5] ... A[1][0] A[1][6] A[1][7] ... A[2][0] A[2][8] A[2][9] ...
Do đó, nếu bạn sử dụng điều này cho vòng lặp:
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
// Do something with A[i][j]
}
}
Sau đó, bạn sẽ có được địa phương tuyệt vời, bởi vì bạn sẽ truy cập các phần tử mảng theo thứ tự xuất hiện trong bộ nhớ. Điều này làm cho số lần đọc của bộ nhớ chính rất nhỏ, vì tất cả mọi thứ thường là trong bộ nhớ cache và sẵn sàng để đi.
Tuy nhiên, nếu bạn trao đổi các vòng lặp, như bạn đã làm, truy cập của bạn nhảy xung quanh trong bộ nhớ và không nhất thiết phải liên tiếp. Điều này có nghĩa là bạn sẽ có rất nhiều bộ nhớ cache bỏ lỡ trong đó địa chỉ bộ nhớ bạn đọc tiếp theo không có trong bộ nhớ cache. Điều này làm tăng số lượng tải bộ nhớ cache, có thể làm chậm đáng kể chương trình.
Trình biên dịch bắt đầu đủ thông minh để trao đổi các vòng như thế này một cách tự động, nhưng chúng tôi vẫn còn cách để tránh bỏ qua các chi tiết này. Như một quy tắc chung, khi viết mã C hoặc C++ cho các mảng đa chiều, hãy cố gắng lặp lại theo thứ tự hàng lớn hơn là thứ tự cột lớn. Bạn có thể nhận được tốc độ đáng chú ý trong chương trình của bạn.
Hy vọng điều này sẽ hữu ích!
Địa phương tham chiếu: bạn đang vô hiệu hóa bộ nhớ cache CPU không hiệu quả theo cách "chậm". – dlev
@dlev: tại sao bạn không đăng câu trả lời này? –
vì dlev không phải về đại diện. dlev là về tình yêu – Robotnik