如何在omp pragma

时间:2016-03-02 12:43:50

标签: c arrays multithreading visual-studio openmp

我正在将现有库从单线程修改为多线程。我有类似下面提供的代码。我无法理解如何声明数组x,y,array1,array2。其中我应该声明为share或threadprivate。我需要使用冲洗吗?如果是,在哪种情况下?

//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];
}

1 个答案:

答案 0 :(得分:0)

我在您的代码中看到的问题在于此行

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

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

现在关键的问题是要知道array2是否可以为不同的索引具有相同的值:

  • 如果您确定j1 != j2 array2[j1] != array2[j2] j1 != j2,那么您的代码就可以并行了。
  • 如果您有array[j1] == array[j2]的值array1,那么您对i的迭代具有依赖关系,并且代码不再(简单和/或有效)并行化。< / LI>

因此,假设我们处于前一种情况,那么您已经在代码中的OpenMP指令就足够了:

  • private需要为x,但隐式已经是因为它是并行化循环的索引;
  • yshared应该是i(默认情况下都是这样),因为他们的访问索引是并行分布的索引(即array2)所以他们的并行更新不重叠;
  • shared只能在阅读模式下访问,因此它没有脑子array1(默认情况下再次);
  • 读取和写入
  • shared,但由于我们最初的假设,线程之间不存在可能的冲突,因为它们的访问索引集将被分离。因此,默认的array2限定符可以正常工作。

但是现在,如果我们在array1允许非脱离索引集访问array1的情况下,我们将不得不保留{{1}的这些访问/更新的顺序}}。这可以使用ordered子句/指令来完成。由于我们仍然希望并行化(有些)有效,我们必须在schedule(static,1)指令中添加parallel子句。有关详细信息,请参阅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 { {1}}和x。因此,使用这些的每个线程副本并且仅在最后更新全局数组可能更有利。然后代码片段的中心部分看起来像这样(根本没有测试):

y

这将避免错误共享,但会增加内存访问次数和并行化开销。所以它毕竟可能不是很有益。您必须在实际代码中亲自查看。

顺便说一句,#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); 仅循环到100时这一事实在相应的数组声明为1000000长时对我来说是可疑的。如果100确实是循环的正确大小,那么可能并行化并不值得...

修改

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

由于我很确定你并没有真正对此感兴趣,我不会再进一步​​发展(这本身就是一个完整的问题),但我会使用(不是这么好)random()把戏。如果您想了解有关生成良好并行随机序列的可能替代方案的更多详细信息,请提出另一个问题。

没有问题来自rand_r()(脱离索引集)的情况,代码将成为:

array2

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