如何在 omp pragma 中声明数组

How to declare arrays in omp pragma

我正在将现有库从单线程修改为多线程。我有如下提供的代码。我不明白如何声明数组 x、y、array1、array2。我应该将它们中的哪一个声明为共享或线程私有。我需要使用冲洗吗?如果是,在哪种情况下?

//global variables
static int array1[100000];
static int array2[100000]; 

//part of program code from one of function. 
int i
int x[1000000];
int y[1000000];

#pragma omp parallel for  
for(i=0, i<100; i++)
{
  y[i]  = i*i-3*i-10*random();
  x[i] = myfunc(i, y[i])
}

//additional function
int myfunc(j, z)
int j,
int z[]
{
  array1[array2[j]] += z[j]+j;
  return array1[j];
}

我在你的代码中看到的问题是在这一行

array1[array2[j]] += z[j]+j;

这意味着 array1 可能会被任何 j 索引修改。而函数myfunc()上下文中的j对应上层索引i。问题是 i 是循环并行化的索引,因此,这意味着 array1 可以在任何时刻由任何线程同时修改。

现在的关键问题是array2是否可以对不同的索引有相同的值:

  • 如果您确定对于任何 j1 != j2 您有 array2[j1] != array2[j2],那么您的代码是可以并行化的。
  • 如果有值 j1 != j2,您有 array[j1] == array[j2],那么您在 array1 的迭代中具有依赖关系,并且代码不再(简单地 and/or 有效) 可并行化。

所以假设我们是前一种情况,那么代码中已有的 OpenMP 指令就足够了:

  • i 需要是 private 但已经隐含了,因为它是并行循环的索引;
  • xy 应该是 shared(默认情况下是),因为它们的访问索引是并行分布的(即 i ) 因此它们的并行更新不会重叠;
  • array2 仅在读取模式下访问,因此很简单 shared(默认情况下也是如此);
  • array1 被读取和写入,但由于我们最初的假设,线程之间不可能发生冲突,因为它们访问它的索引集是分离的。因此,默认的 shared 限定符就可以正常工作。

但是现在,如果我们在 array2 允许非分离索引集访问 array1 的情况下,我们将不得不保留这些访问/更新的顺序 array1。这可以通过 ordered 子句/指令来完成。由于我们仍然希望并行化(某种程度上)有效,因此我们必须在 parallel 指令中添加一个 schedule(static,1) 子句。有关详细信息,请参阅 this great answer。您的代码现在看起来像这样:

//global variables
static int array1[100000];
static int array2[100000]; 

//part of program code from one of function. 
int i
int x[1000000];
int y[1000000];

#pragma omp parallel for schedule(static,1) ordered
for(i=0; i<100; i++)
{
  y[i] = i*i-3*i-10*random();
  x[i] = myfunc(i, y[i])
}

//additional function
int myfunc(j, z)
int j,
int z[]
{
  int tmp = z[j]+j;
  #pragma omp ordered
  array1[array2[j]] += tmp;
  return array1[j];
}

这(我认为)可以工作并且在并行性方面还不错(对于有限数量的线程),但这有一个很大的(巨大的)缺陷:它会产生大量的 false sharing 而更新 xy。因此,使用这些的一些每线程副本并只在最后更新全局数组可能更有利。代码片段的中心部分看起来像这样(根本没有测试):

#pragma omp parallel
#pragma omp single    
int nbth = omp_get_num_threads();

int *xm = malloc(1000000*nbth*sizeof(int));
int *ym = malloc(1000000*nbth*sizeof(int));
#pragma omp parallel
{
    int tid = omp_get_thread_num();
    int *xx = xm+1000000*tid;
    int *yy = ym+1000000*tid;
    #pragma omp for schedule(static,1) ordered
    for(i=0; i<100; i++)
    {
        yy[i] = i*i-3*i-10*random();
        xx[i] = myfunc(i, y[i])
    }
    #pragma omp for
    for (i=0; i<100; i++)
    {
        int j;
        x[i] = 0;
        y[i] = 0;
        for (j=0; j<nbth; j++)
        {
            x[i] += xm[j*1000000+i];
            y[i] += ym[j*1000000+i];
        }
    }
}
free(xm);
free(ym);

这样可以避免虚假共享,但会增加内存访问次数和并行化开销。所以它可能终究不是很有用。您必须在实际代码中亲自查看。

顺便说一句,当相应的数组被声明为 1000000 长时,i 只循环到 100 的事实在我看来很可疑。如果 100 确实是循环的正确大小,那么并行化可能无论如何都不值得...


编辑:

正如 Jim Cownie 在评论中指出的那样,我错过了对 random() 的调用作为跨迭代的依赖源,从而阻止了适当的并行化。我不确定这在您的实际代码的上下文中有多相关(我怀疑您是否真的用随机数据填充了 y 数组)但如果您这样做,则必须更改此部分以便并行执行(否则,生成随机数序列所需的序列化只会杀死并行化带来的任何收益)。但是并行生成不相关的伪随机序列并不像听起来那么简单。您可以使用 rand_r() 而不是 random() 作为 RNG 的线程安全替代方案,并将其每线程种子初始化为不同的值。但是,您不确定一个线程的系列是否会很快与另一个线程的系列发生冲突(一段时间后一个线程开始生成与另一个线程完全相同的系列,搞乱了您预期的渐近行为)。

因为我很确定你对此并不真正感兴趣,所以我不会进一步展开(这本身就是一个完整的问题),但我只会使用(不太好)rand_r()绝招。如果您想了解有关生成良好平行随机序列的可能替代方案的更多详细信息,请提出另一个问题。

如果array2没有问题(disjoin sets of indexes),代码会变成:

// global variable
unsigned int seed;
#pragma omp threadprivate(seed)

// done just once somewhere
#pragma omp parallel
seed = omp_get_thread_num(); //or something else, but different for each thread

// then the parallelised loop
#pragma omp parallel for  
for(i=0; i<100; i++)
{
  y[i]  = i*i-3*i-10*rand_r(&seed);
  x[i] = myfunc(i, y[i])
}

那么除了已经描述的内容之外,其他情况也必须使用相同的技巧。但同样,请记住,这对于基于 RNG 的严肃计算(如蒙特卡洛方法)来说还不够好。如果您只想生成一些用于测试目的的值,它就可以完成工作,但它不会通过任何严格的统计质量测试。