用OpenMP并行填充直方图(数组缩减),而不使用临界区域
我想用OpenMP并行填充直方图。 我在C / C ++中用OpenMP提出了两种不同的方法。
第一种方法proccess_data_v1
为每个线程创build一个专用直方图variableshist_private
, hist_private
填充它们,然后将专用直方图合并到共同直方图hist
的critical
区域中。
第二种方法proccess_data_v2
创build一个数组大小与线程数相等的直方图共享数组,并行填充该数组,然后对共享直方图hist
进行并行求和。
第二种方法似乎优于我,因为它避免了一个临界部分并且并行地对直方图求和。 但是,它需要知道线程的数量并调用omp_get_thread_num()
。 我通常尽量避免这种情况。 有没有更好的方法来执行第二个方法,而不引用线程号和使用大小等于线程数的共享数组?
void proccess_data_v1(float *data, int *hist, const int n, const int nbins, float max) { #pragma omp parallel { int *hist_private = new int[nbins]; for(int i=0; i<nbins; i++) hist_private[i] = 0; #pragma omp for nowait for(int i=0; i<n; i++) { float x = reconstruct_data(data[i]); fill_hist(hist_private, nbins, max, x); } #pragma omp critical { for(int i=0; i<nbins; i++) { hist[i] += hist_private[i]; } } delete[] hist_private; } } void proccess_data_v2(float *data, int *hist, const int n, const int nbins, float max) { const int nthreads = 8; omp_set_num_threads(nthreads); int *hista = new int[nbins*nthreads]; #pragma omp parallel { const int ithread = omp_get_thread_num(); for(int i=0; i<nbins; i++) hista[nbins*ithread+i] = 0; #pragma omp for for(int i=0; i<n; i++) { float x = reconstruct_data(data[i]); fill_hist(&hista[nbins*ithread], nbins, max, x); } #pragma omp for for(int i=0; i<nbins; i++) { for(int t=0; t<nthreads; t++) { hist[i] += hista[nbins*t + i]; } } } delete[] hista; }
编辑:基于@HristoIliev的build议,我创build了一个名为process_data_v3
的改进方法
#define ROUND_DOWN(x, s) ((x) & ~((s)-1)) void proccess_data_v2(float *data, int *hist, const int n, const int nbins, float max) { int* hista; #pragma omp parallel { const int nthreads = omp_get_num_threads(); const int ithread = omp_get_thread_num(); int lda = ROUND_DOWN(nbins+1023, 1024); //1024 ints = 4096 bytes -> round to a multiple of page size #pragma omp single hista = (int*)_mm_malloc(lda*sizeof(int)*nthreads, 4096); //align memory to page size for(int i=0; i<nbins; i++) hista[lda*ithread+i] = 0; #pragma omp for for(int i=0; i<n; i++) { float x = reconstruct_data(data[i]); fill_hist(&hista[lda*ithread], nbins, max, x); } #pragma omp for for(int i=0; i<nbins; i++) { for(int t=0; t<nthreads; t++) { hist[i] += hista[lda*t + i]; } } } _mm_free(hista); }
您可以在并行区域内分配大数组,在那里您可以查询正在使用的线程的实际数量:
int *hista; #pragma omp parallel { const int nthreads = omp_get_num_threads(); const int ithread = omp_get_thread_num(); #pragma omp single hista = new int[nbins*nthreads]; ... } delete[] hista;
为了获得更好的性能,我build议将hista
中每个线程块的大小hista
到系统内存页面大小的倍数,即使这可能会在不同的部分直方图之间留下空洞。 通过这种方式,您可以防止在NUMA系统上发生虚假共享和远程内存访问(但不是在最终缩减阶段)。