如何在每个例程OpenMP中处理子数组

时间:2013-10-17 13:30:00

标签: c++ arrays parallel-processing openmp

我有一些代码,并行计算某些数组前缀的总和(例如out_arr[0] = in_arr[0]out_arr[1] = in_arr[0]+in_arr[1]等)。 我的代码有N个线程,N是一些in_arr个元素,每个线程只处理1个数组元素。这不是一个好的解决方案,所以我想在每个线程中处理N/num_of_threads,但我失败了。

我尝试使用N/num_of_threads值创建共享变量,并使用此for指令后面的此变量组织#pragma循环,但我无法在stdout中调试这些幻数。

这是“坏”解决方案的工作版本:

void CalcSum2(int a[], int s[], int n) { 
    int* old = new int [n], *cnt = new int [n]; 
    #pragma omp parallel num_threads(N) {
    int i = omp_get_thread_num(), d = 1; 
    s[i] = a[i]; 
    cnt[i] = 1; 
     #pragma omp barrier 
    while (d < n) { 
        old[i] = s[i]; 
     #pragma omp barrier 
         if (i >= d) { 
             s[i] += old[i-d]; 
         cnt[i]++; 
         } 
         d += d; 
     #pragma omp barrier 
    }
    }
    delete[] old; delete[] cnt; 
    return; 
} 

2 个答案:

答案 0 :(得分:1)

您与扫描并行的方式使用了太多可能影响性能的障碍。

多核CPU上的并行扫描效率不高,因为总和操作的数量从n-1增加到约2n。因此,时间成本为2n/m,其中m是CPU核心数。

要减少障碍数量,您可以先对数据的每个段进行顺序扫描,然后为每个段结果添加适当的偏移量。以下代码演示了这个想法。当len为1G时,它在8核CPU上加速 2.4x 。您仍然可以改进第二部分以获得更高的性能。

inline void scan(int a[], int s[], int len)
{
    int sum=0.0;
    for(int i=0;i<len;i++) {
        sum+=a[i];
        s[i]=sum;
    }
}

void ParallelScan(int a[], int s[], int len)
{
    int nt;
    int seglen, subseglen;
    int* segsum;
    #pragma omp parallel
    {
        #pragma omp single
        {
            nt = omp_get_num_threads();
            seglen = (len+nt-1)/nt;
            subseglen = (seglen+nt-1)/nt;
            segsum = new int[nt];
        }
        int tid = omp_get_thread_num();
        int start = seglen*tid;
        int end = seglen*(tid+1);
        end = end > len ? len : end;

        scan(&a[start],&s[start],end-start);
        segsum[tid]=s[end-1];
        #pragma omp barrier

        #pragma omp single
        for(int i=1; i<nt; i++) {
            segsum[i]+=segsum[i-1];
        }

        for(int segid=1; segid<nt; segid++) {
            int segstart=seglen*segid;
            int start = segstart + subseglen*tid;
            int end = start + subseglen;
            end = end > len ? len : end;
            end = end > segstart+seglen ? segstart+seglen : end;

            int offset = segsum[segid-1];
            for(int i=start; i<end; i++) {
                s[i]+=offset;
            }
        }
    }


    delete[] segsum;
}

答案 1 :(得分:1)

你正在做累积金额。也称为前缀和。这可以与OpenMP并行完成。我最近使用OpenMP Parallel cumulative (prefix) sums in OpenMP: communicating values between threads

解决了这个问题

您必须并行运行两次数组。第一次进行部分求和,第二次用偏移量校正部分和。

我在下面为您转换了代码。与测试一样,我做了计数数的总和,它具有i*(i+1)/2的封闭形式解。你可以看到prefix_sum函数得到了正确的答案。

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

void prefix_sum(int a[], int s[], int n) {
    int *suma;
    #pragma omp parallel
    {
        const int ithread = omp_get_thread_num();
        const int nthreads = omp_get_num_threads();
        #pragma omp single
        {
            suma = new int[nthreads+1];
            suma[0] = 0;
        }
        int sum = 0;
        #pragma omp for schedule(static) nowait // do partial sum in parallel
        for(int i=0; i<n; i++) {
            sum += a[i];
            s[i] = sum;
        }
        suma[ithread+1] = sum;
        #pragma omp barrier
        int offset = 0;
        for(int i=0; i<(ithread+1); i++) {
            offset += suma[i];
        }

        #pragma omp for schedule(static) //run over array again in parallel for full sum
        for(int i=0; i<n; i++) {
            s[i] += offset;
        }
    }
    delete[] suma;
}

int main() {
    const int n = 100;
    int *a = new int[n];
    int *s = new int[n];
    for(int i=0; i<n; i++) {
        a[i] = i;
    }
    prefix_sum(a, s, n);
    for(int i=0; i<n; i++) {
        printf("%d ", s[i]);
    } printf("\n");

    for(int i=0; i<n; i++) {
        printf("%d ", i*(i+1)/2);
    } printf("\n");
}

修改 该方法的一个问题是,对于大型阵列,大多数值在第二次传递开始时已从高速缓存中逐出。我提出了一个并行运行在块上的解决方案,然后依次移动到下一个块。我将chunck_size设置为二级缓存(实际上由于有四个核心而四次)。这为较大的阵列提供了很大的改进。这是功能的概述。完整的功能可以在simd-prefix-sum-on-intel-cpu的答案中找到。

void scan_omp_SSEp2_SSEp1_chunk(float a[], float s[], int n) {
    float *suma;
    const int chunk_size = 1<<18;
    const int nchunks = n%chunk_size == 0 ? n / chunk_size : n / chunk_size + 1;    
    #pragma omp parallel
    {
        //initialization code 
        for (int c = 0; c < nchunks; c++) {
            const int start = c*chunk_size;
            const int chunk = (c + 1)*chunk_size < n ? chunk_size : n - c*chunk_size; 
            //pass1: pass1_SSE(&a[start], &s[start], chunk);                
            //get offset
            //pass2: pass2_SSE(&s[start], offset, chunk);
        }
    }
    delete[] suma;
}