在不使用临界区的情况下,与OpenMP并行填充直方图(数组缩减)

9 openmp histogram

我想使用OpenMP并行填充直方图.我在C/C++中使用OpenMP提出了两种不同的方法.

第一种方法为每个线程proccess_data_v1创建一个私有直方图变量hist_private,并行填充它们,然后将私有直方图加到hist一个critical部分中的共享直方图中.

第二种方法生成proccess_data_v2一个直方图的共享数组,其数组大小等于线程数,并行填充此数组,然后并行地对共享直方图求和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;
}
Run Code Online (Sandbox Code Playgroud)

编辑: 根据@HristoIliev的建议,我创建了一个名为的改进方法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);
}
Run Code Online (Sandbox Code Playgroud)

Hri*_*iev 5

您可以在并行区域内分配大数组,您可以在其中查询正在使用的实际线程数:

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;
Run Code Online (Sandbox Code Playgroud)

为了获得更好的性能,我建议您将每个线程块的大小四舍五入为hista系统内存页面大小的倍数,即使这可能会在不同的部分直方图之间留下漏洞。通过这种方式,您将防止 NUMA 系统上的错误共享和远程内存访问(但不会在最后的缩减阶段)。