我有一些代码,并行计算某些数组前缀的总和(例如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;
}
答案 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;
}