2012-03-30 31 views
7

Tôi không biết cách tối ưu hóa hiệu suất bộ nhớ cache ở mức thực sự thấp, suy nghĩ về kích thước bộ nhớ cache hoặc kết hợp. Đó không phải là điều bạn có thể học qua đêm. Xem xét chương trình của tôi sẽ chạy trên nhiều hệ thống và kiến ​​trúc khác nhau, tôi không nghĩ rằng nó sẽ có giá trị nó anyway. Nhưng vẫn còn, có lẽ một số bước tôi có thể làm để giảm bớt bộ nhớ cache nhớ nói chung.C++: Cải thiện hiệu suất bộ nhớ cache trong mảng 3D

Đây là một mô tả về vấn đề của tôi:

Tôi có một mảng 3d của số nguyên, đại diện cho các giá trị tại các điểm trong không gian, như [x] [y] [z]. Mỗi kích thước có cùng kích thước, vì vậy nó giống như một khối lập phương. Từ đó tôi cần tạo một mảng 3D khác, trong đó mỗi giá trị trong mảng mới này là một hàm của 7 tham số: giá trị tương ứng trong mảng 3d gốc, cộng với 6 chỉ số "chạm" nó trong không gian. Tôi không lo lắng về các cạnh và góc của khối lập phương bây giờ.

Dưới đây là những gì tôi có nghĩa là trong C++:

void process3DArray (int input[LENGTH][LENGTH][LENGTH], 
        int output[LENGTH][LENGTH][LENGTH]) 
{ 
    for(int i = 1; i < LENGTH-1; i++) 
     for (int j = 1; j < LENGTH-1; j++) 
      for (int k = 1; k < LENGTH-1; k++) 
      //The for loops start at 1 and stop before LENGTH-1 
      //or other-wise I'll get out-of-bounds errors 
      //I'm not concerned with the edges and corners of the 
      //3d array "cube" at the moment. 
      { 
       int value = input[i][j][k]; 

       //I am expecting crazy cache misses here: 
       int posX = input[i+1] [j] [k]; 
       int negX = input[i-1] [j] [k]; 
       int posY = input[i] [j+1] [k]; 
       int negY = input[i] [j-1] [k]; 
       int posZ = input[i] [j] [k+1]; 
       int negZ = input[i] [j] [k-1]; 

       output [i][j][k] = 
        process(value, posX, negX, posY, negY, posZ, negZ); 
      } 
} 

Tuy nhiên, nó có vẻ như nếu LENGTH là đủ lớn, tôi sẽ nhận được tấn cache nhớ khi tôi đang lấy các tham số cho process. Có một cách thân thiện với bộ nhớ cache để làm điều này, hoặc một cách tốt hơn để đại diện cho dữ liệu của tôi khác với một mảng 3D?

Và nếu bạn có thời gian trả lời các câu hỏi bổ sung này, tôi có phải xem xét giá trị LENGTH không? Giống như nó khác nhau cho dù LENGTH là 20 vs 100 vs 10000. Ngoài ra, tôi sẽ phải làm cái gì khác nếu tôi sử dụng một cái gì đó khác hơn là số nguyên, giống như một cấu trúc 64-byte?

@ ildjarn:

Xin lỗi, tôi không nghĩ rằng mã mà tạo ra các mảng tôi đi qua vào process3DArray quan trọng. Nhưng nếu có, tôi muốn biết tại sao.

int main() { 
    int data[LENGTH][LENGTH][LENGTH]; 
    for(int i = 0; i < LENGTH; i++) 
     for (int j = 0; j < LENGTH; j++) 
      for (int k = 0; k < LENGTH; k++) 
       data[i][j][k] = rand() * (i + j + k); 

    int result[LENGTH][LENGTH][LENGTH]; 
    process3DArray(data, result); 
} 
+0

"Tấn" nghĩa là gì? Bạn mong đợi bao nhiêu? –

+0

Tôi không biết thực sự. Tôi có lẽ sẽ nhận được một bộ nhớ cache bỏ lỡ cho posX, negX, posY, và negY, nhưng có thể không cho posZ và negZ, vì những người có địa phương tốt hơn. – newprogrammer

+0

http://en.wikipedia.org/wiki/Loop_tiling – Anycorn

Trả lời

3

Điều quan trọng nhất bạn đã có quyền. Nếu bạn đang sử dụng Fortran, bạn sẽ làm điều đó sai, nhưng đó là một câu chuyện khác. Những gì bạn có quyền là bạn đang xử lý trong vòng lặp bên trong theo hướng mà địa chỉ bộ nhớ gần nhất với nhau. Một lần tìm nạp bộ nhớ (ngoài bộ nhớ cache) sẽ kéo theo nhiều giá trị, tương ứng với một loạt các giá trị liền kề của k. Bên trong vòng lặp của bạn, bộ đệm sẽ chứa một số giá trị từ i, j; một số tương tự từ i +/- 1, j và từ i, j +/- 1. Vì vậy, về cơ bản bạn có năm phần rời rạc của bộ nhớ hoạt động. Đối với các giá trị nhỏ LENGTH, các giá trị này sẽ chỉ là 1 hoặc 3 phần bộ nhớ. Đó là trong bản chất của cách thức lưu trữ được xây dựng mà bạn có thể có nhiều hơn này nhiều phần rời rạc của bộ nhớ trong tập hoạt động của bạn.

Tôi hy vọng quá trình() nhỏ và nội tuyến. Nếu không thì điều này cũng có thể không đáng kể. Ngoài ra, nó sẽ ảnh hưởng đến việc mã của bạn có phù hợp với bộ nhớ cache lệnh hay không.

Vì bạn quan tâm đến hiệu suất, nên luôn khởi động năm con trỏ (bạn chỉ cần một con trỏ cho giá trị, posZ và negZ), và sau đó dùng * (p ++) bên trong vòng lặp.

input[i+1] [j] [k]; 

yêu cầu trình biên dịch tạo thêm 3 và hai nhân, trừ khi bạn có trình tối ưu hóa rất tốt. Nếu trình biên dịch của bạn đặc biệt lười về việc phân bổ đăng ký, bạn cũng nhận được bốn truy cập bộ nhớ; bằng không.

*inputIplusOneJK++ 

yêu cầu thêm một tham chiếu và bộ nhớ.

+0

Xin lỗi nếu tôi hiểu sai bạn (Tôi không hiểu các từ như "tập hoạt động" và "đăng ký phân bổ"). Làm thế nào tôi đọc câu trả lời của bạn là: Mã của tôi ngay bây giờ sẽ không bị thiếu bộ nhớ cache cho mỗi lần lặp của vòng lặp thứ 3 cho vòng lặp, và sau đó bạn tiếp tục mô tả một số tối ưu hóa nhỏ. Tôi gần như chắc chắn mã này sẽ không gây ra một tấn bộ nhớ cache lệnh nhớ. Nếu đó là chính xác, sau đó cảm ơn bạn rất rất nhiều cho câu trả lời của bạn. – newprogrammer

+0

Một tập hoạt động là tập hợp các phân đoạn bộ nhớ được sử dụng gần đây - nếu tập hợp hoạt động là đủ nhỏ, tất cả sẽ phù hợp trong bộ nhớ cache. Đăng ký cấp phát là trình biên dịch quyết định biến nào được đặt trong sổ đăng ký và các biến nào để lại trong bộ nhớ. Và vâng, tôi nói bạn không nên có số lượng bộ nhớ cache lớn bất thường. – DRVic

7

Có một câu trả lời cho một câu hỏi tương tự ở đây: https://stackoverflow.com/a/7735362/6210 (bởi tôi!)

Mục đích chính của việc tối ưu hóa một mảng traversal đa chiều là để đảm bảo bạn truy cập mảng như vậy mà bạn có xu hướng sử dụng lại bộ nhớ cache các dòng được truy cập từ bước lặp lại trước đó. Để truy cập từng phần tử của một mảng một lần và chỉ một lần, bạn có thể thực hiện điều này chỉ bằng cách truy cập vào thứ tự bộ nhớ (như bạn đang làm trong vòng lặp của bạn).

Vì bạn đang làm điều gì đó phức tạp hơn việc truyền tải phần tử đơn giản (truy cập phần tử cộng với 6 người hàng xóm), bạn cần chia nhỏ quá trình truyền tải của mình để không truy cập quá nhiều dòng bộ nhớ cache cùng một lúc. Vì việc tạm dừng bộ đệm được chi phối bằng cách di chuyển dọc theo jk, bạn chỉ cần sửa đổi truyền tải sao cho bạn truy cập vào các khối cùng một lúc thay vì các hàng tại một thời điểm.

Ví dụ:

const int CACHE_LINE_STEP= 8; 

void process3DArray (int input[LENGTH][LENGTH][LENGTH], 
        int output[LENGTH][LENGTH][LENGTH]) 
{ 
    for(int i = 1; i < LENGTH-1; i++) 
     for (int k_start = 1, k_next= CACHE_LINE_STEP; k_start < LENGTH-1; k_start= k_next; k_next+= CACHE_LINE_STEP) 
     { 
      int k_end= min(k_next, LENGTH - 1); 

      for (int j = 1; j < LENGTH-1; j++) 
       //The for loops start at 1 and stop before LENGTH-1 
       //or other-wise I'll get out-of-bounds errors 
       //I'm not concerned with the edges and corners of the 
       //3d array "cube" at the moment. 
      { 
       for (int k= k_start; k<k_end; ++k) 
       { 
        int value = input[i][j][k]; 

        //I am expecting crazy cache misses here: 
        int posX = input[i+1] [j] [k]; 
        int negX = input[i-1] [j] [k]; 
        int posY = input[i] [j+1] [k]; 
        int negY = input[i] [j-1] [k]; 
        int posZ = input[i] [j] [k+1]; 
        int negZ = input[i] [j] [k-1]; 

        output [i][j][k] = 
         process(value, posX, negX, posY, negY, posZ, negZ); 
       } 
      } 
     } 
} 

gì điều này trong đảm bảo rằng bạn không thrash cache bằng cách truy cập mạng lưới một cách định hướng khối (trên thực tế, giống như một cách định hướng cột mỡ bao quanh bởi dòng bộ nhớ cache kích thước). Nó không hoàn hảo vì có những chồng chéo vượt qua các dòng bộ nhớ cache giữa các cột, nhưng bạn có thể tinh chỉnh nó để làm cho nó tốt hơn.

+0

Cảm ơn rất nhiều – newprogrammer

+2

Tôi rất muốn thấy một số bằng chứng cho thấy điều này tạo nên sự khác biệt, bởi vì nó chỉ có 5 phần rời rạc của bộ nhớ đang được sử dụng. – DRVic

Các vấn đề liên quan