为什么此代码的并行执行速度慢于顺序执行?

时间:2013-10-06 18:36:03

标签: c parallel-processing openmp

#pragma omp parallel
{
 for (i=1; i<1024; i++)
  #pragma omp for
  for (j=1; j<1024; j++)
   A[i][j] = 2*A[i-1][j];
}

我使用12个线程来执行此代码。 有什么建议我必须做些什么才能加快?

2 个答案:

答案 0 :(得分:1)

假设A的类型小于64Bytes,尝试以这种方式并行化内部循环很可能会导致您在缓存行中进行错误共享。

假设A是一个4字节整数的对齐数组,在同一个缓存行中你会得到A [i] [0]到A [i] [15]。这意味着所有12个线程都会尝试同时读取该线路,每个线程都需要它所需的部分,这可能导致在多个核心之间共享该线路,如果您将其保留在该线路上,但您也尝试写回来,引导每个核心尝试取得线上的所有权以进行修改。

CPU缓存通常基于基于MESI的协议,使存储尝试发出读取所有权,这将使请求者之外的每个其他核心的行无效。发出12个并行(或者更确切地说是6个,如果你有6个核心* 2个线程)会导致一场比赛,第一个赢得该线路的人可能会在他甚至有机会之前通过一个窥探从他身上抢先一步。修改它(虽然这不太可能)。结果非常混乱,并且可能需要一段时间才能在线路轮流到每个核心,进行修改,然后被另一个核心窥探。这对于16个元素的下一个连续组中的每一个都重复出现(再次假设为int)。

你可能会做的是:

  • 确保每个单独的线程都在自己的缓存行上工作,但是添加一个内部循环,该循环在每行上运行所需数量的元素,并并行化跳过这些元素数量的循环。

然而,当您丢失代码的空间局部性和流式属性时,这会阻止您充分发挥CPU的潜力。相反,你可以:

  • 并行化外部循环,使每个线程在几行上工作,从而允许它拥有整个连续的内存流。但是,由于您需要在线之间进行排序,因此您可能需要在此进行一些调整(例如转置)。

这里仍然有一个缺点,因为如果一个线程遇到太多的流,它可能会松开它们的轨道。因此,第三种方法是

  • 平铺数组 - 将其分成几组,比如48行,在线程之间分配它们,使每个运行几行(转换技巧仍然适用于此,顺便说一句),然后继续下一组

答案 1 :(得分:0)

1)你有多少核心?你不可能获得比这更多的并行性加速,正如其他人所说,可能要少得多。

2)看起来内部索引j应该从0开始,而不是1。

3)内部循环正在为指针和展开而哭泣,如

double* pa = &A[i][0];
double* pa1 = &A[i-1][0];
for (j = 0; j < 1024; j += 8){
    *pa++ = 2 * *pa1++;
    *pa++ = 2 * *pa1++;
    *pa++ = 2 * *pa1++;
    *pa++ = 2 * *pa1++;
    *pa++ = 2 * *pa1++;
    *pa++ = 2 * *pa1++;
    *pa++ = 2 * *pa1++;
    *pa++ = 2 * *pa1++;
}

...或

double* pa = &A[i][0];
double* paEnd = &A[i][1024];
double* pa1 = &A[i-1][0];
for (; pa < paEnd; pa += 8, pa1 += 8){
    pa[0] = 2 * pa1[0];
    pa[1] = 2 * pa1[1];
    pa[2] = 2 * pa1[2];
    pa[3] = 2 * pa1[3];
    pa[4] = 2 * pa1[4];
    pa[5] = 2 * pa1[5];
    pa[6] = 2 * pa1[6];
    pa[7] = 2 * pa1[7];
}

以较快者为准。