2011-07-07 31 views
19

Tôi là một người dùng OpenMP khá kinh nghiệm, nhưng tôi đã gặp một vấn đề khó hiểu, và tôi hy vọng rằng ai đó ở đây có thể giúp đỡ. Vấn đề là một thuật toán băm đơn giản hoạt động tốt cho các mảng được phân bổ stack, nhưng kém cho các mảng trên heap.OpenMP: hiệu suất kém của mảng heap (mảng ngăn xếp hoạt động tốt)

Ví dụ dưới đây sử dụng i% M (i modulus M) để đếm mọi số nguyên M thứ trong phần tử mảng tương ứng. Để đơn giản, hãy tưởng tượng N = 1000000, M = 10. Nếu N% M == 0, thì kết quả nên được rằng mọi phần tử của thùng [] là bằng N/M:

#pragma omp for 
    for (int i=0; i<N; i++) 
    bins[ i%M ]++; 

Mảng thùng [] là tin cho mỗi thread (kết quả tổng hợp I của tất cả các chủ đề trong một phần quan trọng sau đó).

Khi thùng [] được cấp phát trên ngăn xếp, chương trình hoạt động tốt, với tỷ lệ hiệu suất tương ứng với số lượng lõi.

Tuy nhiên, nếu thùng [] nằm trên heap (con trỏ tới thùng [] nằm trên ngăn xếp), hiệu suất giảm mạnh. Và đó là một vấn đề lớn!

Tôi muốn song song binning (băm) của dữ liệu nhất định thành mảng heap với OpenMP và đây là lần truy cập hiệu suất chính.

Nó chắc chắn không phải là một cái gì đó ngớ ngẩn như tất cả các chủ đề cố gắng để viết vào cùng một khu vực của bộ nhớ. Đó là bởi vì mỗi luồng có các thùng [] của riêng nó, kết quả là chính xác với cả thùng phân bổ và ngăn xếp ngăn xếp, và không có sự khác biệt về hiệu suất cho các luồng đơn. Tôi đã sao chép vấn đề trên phần cứng khác (Intel Xeon và AMD Opteron), với trình biên dịch GCC và Intel C++. Tất cả các bài kiểm tra đều có trên Linux (Ubuntu và RedHat).

Có vẻ như không có lý do tại sao hiệu suất tốt của OpenMP nên được giới hạn trong mảng ngăn xếp.

Có dự đoán nào không? Có thể truy cập các luồng tới đống đi qua một số loại cổng chia sẻ trên Linux? Làm cách nào để khắc phục điều đó?

chương trình Complete để chơi xung quanh với dưới:

#include <stdlib.h> 
#include <stdio.h> 
#include <omp.h> 

int main(const int argc, const char* argv[]) 
{ 
    const int N=1024*1024*1024; 
    const int M=4; 
    double t1, t2; 
    int checksum=0; 

    printf("OpenMP threads: %d\n", omp_get_max_threads()); 

    ////////////////////////////////////////////////////////////////// 
    // Case 1: stack-allocated array 
    t1=omp_get_wtime(); 
    checksum=0; 
#pragma omp parallel 
    { // Each openmp thread should have a private copy of 
    // bins_thread_stack on the stack: 
    int bins_thread_stack[M]; 
    for (int j=0; j<M; j++) bins_thread_stack[j]=0; 
#pragma omp for 
    for (int i=0; i<N; i++) 
     { // Accumulating every M-th number in respective array element 
     const int j=i%M; 
     bins_thread_stack[j]++; 
     } 
#pragma omp critical 
    for (int j=0; j<M; j++) checksum+=bins_thread_stack[j]; 
    } 
    t2=omp_get_wtime(); 
    printf("Time with stack array: %12.3f sec, checksum=%d (must be %d).\n", t2-t1, checksum, N); 
    ////////////////////////////////////////////////////////////////// 

    ////////////////////////////////////////////////////////////////// 
    // Case 2: heap-allocated array 
    t1=omp_get_wtime(); 
    checksum=0; 
    #pragma omp parallel 
    { // Each openmp thread should have a private copy of 
    // bins_thread_heap on the heap: 
    int* bins_thread_heap=(int*)malloc(sizeof(int)*M); 
    for (int j=0; j<M; j++) bins_thread_heap[j]=0; 
    #pragma omp for 
    for (int i=0; i<N; i++) 
     { // Accumulating every M-th number in respective array element 
     const int j=i%M; 
     bins_thread_heap[j]++; 
     } 
    #pragma omp critical 
    for (int j=0; j<M; j++) checksum+=bins_thread_heap[j]; 
    free(bins_thread_heap); 
    } 
    t2=omp_get_wtime(); 
    printf("Time with heap array: %12.3f sec, checksum=%d (must be %d).\n", t2-t1, checksum, N); 
    ////////////////////////////////////////////////////////////////// 

    return 0; 
} 

kết quả đầu ra mẫu của chương trình là dưới đây:

cho OMP_NUM_THREADS = 1

OpenMP threads: 1 
Time with stack array: 2.973 sec, checksum=1073741824 (must be 1073741824). 
Time with heap array: 3.091 sec, checksum=1073741824 (must be 1073741824). 

và cho OMP_NUM_THREADS = 10

OpenMP threads: 10 
Time with stack array: 0.329 sec, checksum=1073741824 (must be 1073741824). 
Time with heap array: 2.150 sec, checksum=1073741824 (must be 1073741824). 

Tôi rất cảm kích sự giúp đỡ của bạn!

Trả lời

23

Đây là vấn đề dễ thương: với mã như trên (gcc4.4, Intel i7) với 4 chủ đề tôi nhận được

OpenMP threads: 4 
Time with stack array:  1.696 sec, checksum=1073741824 (must be 1073741824). 
Time with heap array:  5.413 sec, checksum=1073741824 (must be 1073741824). 

nhưng nếu tôi thay đổi dòng malloc để

int* bins_thread_heap=(int*)malloc(sizeof(int)*M*1024); 

(Cập nhật: hoặc thậm chí

int* bins_thread_heap=(int*)malloc(sizeof(int)*16); 

)

sau đó tôi nhận được

OpenMP threads: 4 
Time with stack array:  1.578 sec, checksum=1073741824 (must be 1073741824). 
Time with heap array:  1.574 sec, checksum=1073741824 (must be 1073741824). 

Sự cố ở đây là false sharing. Các malloc mặc định đang được rất (không gian) hiệu quả, và đưa các yêu cầu phân bổ nhỏ tất cả trong một khối bộ nhớ, bên cạnh nhau; nhưng kể từ khi phân bổ quá nhỏ mà nhiều phù hợp trong cùng một dòng bộ nhớ cache, có nghĩa là mỗi lần một thread cập nhật các giá trị của nó, nó dirties dòng bộ nhớ cache của các giá trị trong các chủ đề lân cận. Bằng cách làm cho bộ nhớ được yêu cầu đủ lớn, điều này không còn là vấn đề nữa.

Ngẫu nhiên, phải rõ ràng lý do khiến trường hợp được phân bổ theo chồng không thấy vấn đề này; chủ đề khác nhau - ngăn xếp khác nhau - bộ nhớ đủ xa appart rằng chia sẻ sai không phải là một vấn đề.

Là một điểm phụ - nó không thực sự quan trọng đối với M kích thước bạn đang sử dụng ở đây, nhưng nếu M của bạn (hoặc số lượng các chủ đề) lớn hơn, quan trọng nhất sẽ là một nút cổ chai nối tiếp lớn; bạn có thể sử dụng OpenMP reductions để tính tổng kiểm tra hiệu quả hơn

#pragma omp parallel reduction(+:checksum) 
    { // Each openmp thread should have a private copy of 
     // bins_thread_heap on the heap: 
     int* bins_thread_heap=(int*)malloc(sizeof(int)*M*1024); 
     for (int j=0; j<M; j++) bins_thread_heap[j]=0; 
#pragma omp for 
     for (int i=0; i<N; i++) 
     { // Accumulating every M-th number in respective array element 
      const int j=i%M; 
      bins_thread_heap[j]++; 
     } 
     for (int j=0; j<M; j++) 
      checksum+=bins_thread_heap[j]; 
     free(bins_thread_heap); 
} 
+0

Điều này thật tuyệt, Jonathan, cảm ơn bạn! Vì vậy, nó có nghĩa là cách duy nhất để sử dụng đống hiệu quả là bằng cách lãng phí nó? Có thể một số triển khai của OpenMP có chức năng malloc đặc biệt, tôi sẽ phải nghiên cứu. Nhân tiện, những gì bạn nói về khối quan trọng là nút cổ chai là không chính xác. Khối quan trọng là ở phần cuối của phần song song của tôi, và không nằm trong vòng lặp for. Trong thực tế, mệnh đề 'reduce 'đạt được giảm bằng cách làm chính xác điều đó, đặt một khối quan trọng ở cuối phần song song. Nhưng cảm ơn cho người đứng đầu lên! – drlemon

+2

Ah, nhưng (a) quan trọng là một hoạt động rất nặng, và (b) nó thô hơn cần thiết - bạn có thể làm tổng số địa phương của bạn trước, sau đó chỉ làm quan trọng (hoặc tốt hơn, nguyên tử) để cập nhật toàn cầu tổng hợp. Nhưng ngay cả khi đó, với số lượng lớn các luồng giảm sẽ vẫn nhanh hơn, bởi vì việc giảm cuối cùng có thể được thực hiện theo thứ bậc (trong ln (số lượng chủ đề) thời gian, chứ không phải thời gian (số lượng chủ đề).) –

+2

sử dụng chia sẻ heap - tránh chia sẻ sai là một vấn đề chung cho tất cả các hoạt động bộ nhớ chia sẻ, và cách duy nhất để tránh nó là đảm bảo rằng bạn có các phần rời rạc của bộ nhớ có ít nhất một dòng bộ đệm. Kích thước của khoảng cách đó sẽ phụ thuộc vào hệ thống; làm cho nó nhiều K là overkill, thường là 512 byte hoặc lâu hơn sẽ làm điều đó. –

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