【问题标题】:Fill histograms (array reduction) in parallel with OpenMP without using a critical section在不使用临界区的情况下,与 OpenMP 并行填充直方图(数组缩减)
【发布时间】:2013-05-23 06:12:22
【问题描述】:

我想使用 OpenMP 并行填充直方图。我想出了两种不同的方法来使用 C/C++ 中的 OpenMP 执行此操作。

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

第二种方法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;
}

编辑: 根据@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);
}

【问题讨论】:

  • 您能解释一下为什么要使用嵌套并行区域吗? (我指的是您的 process_data_v1 方法)。也许我不理解某些东西,但是根据您的代码,在我看来,您要求的是 Nthreads**2。也就是说,您要求的资源比可用的资源多。那是对的吗?换句话说,你能解释一下外部平行区域的行为吗?谢谢...
  • 嗨@user2088790,proccess_data_v1 不是最快的吗?因为我们不需要共享内存。我尝试了版本 2 和 3,它们比 v1 慢。有什么建议吗?

标签: openmp histogram


【解决方案1】:

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

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;

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

【讨论】:

  • 谢谢。我实施了您的建议,这绝对是一个更好的解决方案。我需要阅读页面大小。我认为确保 hista 中的块是缓存行大小(64 字节)的倍数就足以防止错误共享。例如,如果 nbins 是 64 的倍数(并且 hista 的地址也是 64 的倍数),这不会防止虚假共享吗?
  • @Hristolliev,我在您的建议中添加了一些代码。我将夹头尺寸称为 lda 并将其设为 64 的倍数。我是否应该使用不同的值,例如4KB = 页面大小?
  • 如果您在 NUMA 系统上运行,例如多插槽 AMD64 或现代 Xeon 机器,那么您应该四舍五入到 4 KiB。此外,一旦确定了正确舍入的大小,请使用 posix_memalign 分配与页面边界对齐的内存。
  • 如果不在页面边界上,也要在缓存行边界上对齐,否则即使有正确大小的块也可能导致错误共享。
  • 这真的取决于使用的内存管理器。例如,在某些发行版上,glibc 被配置为使用每个线程的 arenas,并且每个线程都有自己的堆空间。较大的分配通常以匿名mmaps 的形式实现,因此总是获得新页面。但是哪个线程分配内存并不重要。哪个踏面首先触及每个特定页面很重要——Linux 上当前的 NUMA 策略是“第一次触及”,即物理内存页面来自 NUMA 节点,第一次触及该页面的代码在该节点运行。
猜你喜欢
  • 2016-05-12
  • 1970-01-01
  • 1970-01-01
  • 2021-12-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-08
  • 1970-01-01
相关资源
最近更新 更多