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!
Đ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
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ủ đề).) –
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 đó. –