OpenMP错误共享和缓存命中利用

时间:2013-10-19 14:11:28

标签: c for-loop matrix parallel-processing openmp

在我的代码中,有几种方法包含用于在ny * nx矩阵中移动的嵌套循环。我想并行化这个过程,所以我在每个方法上使用了类似的东西:

#pragma omp parallel for private(jj,x_e,x_w,y_n,y_s)
  for(ii=0;ii<ny;ii++) {
    for(jj=0;jj<nx;jj++) {
      /* determine indices of axis-direction neighbours
      ** respecting periodic boundary conditions (wrap around) */
      y_n = (ii + 1) % ny;
      x_e = (jj + 1) % nx;
      y_s = (ii == 0) ? (ii + ny - 1) : (ii - 1);
      x_w = (jj == 0) ? (jj + nx - 1) : (jj - 1);
      //propagate densities to neighbouring cells, following
      tmp[ii *nx + jj].speeds[0]  = cells[ii*nx + jj].speeds[0]; /* central cell, */
                                                                                     /* no movement   */
      tmp[ii *nx + x_e].s[1] = cells[ii*nx + jj].s[1]; /* east */
      tmp[y_n*nx + jj].s[2]  = cells[ii*nx + jj].s[2]; /* north */
      tmp[ii *nx + x_w].s[3] = cells[ii*nx + jj].s[3]; /* west */
      tmp[y_s*nx + jj].s[4]  = cells[ii*nx + jj].s[4]; /* south */
      tmp[y_n*nx + x_e].s[5] = cells[ii*nx + jj].s[5]; /* north-east */
      tmp[y_n*nx + x_w].s[6] = cells[ii*nx + jj].s[6]; /* north-west */
      tmp[y_s*nx + x_w].s[7] = cells[ii*nx + jj].s[7]; /* south-west */      
      tmp[y_s*nx + x_e].s[8] = cells[ii*nx + jj].s[8]; /* south-east */      
    }
  }

然而,这段代码(以及其他代码)非常慢。有什么方法可以纠正我的#pragma语句并重写数据结构或循环以使其缓存友好并避免错误共享?

PS:代码是用-O3编译的,因此每次尝试进行小规模优化都没有达到任何加速。

2 个答案:

答案 0 :(得分:0)

设置线程并将工作分配给它们会带来一些开销。由于您的工作量很小(200x300),而且工作很简单(只有一些数据副本),因此线程开销可能比实际工作量大,这也是您无法提高性能的原因之一。 / p>

另一个原因是由于使用了结构数组(AoS),您的代码缓存本地化效果不佳。特别是在写tmp时。为了获得更好的缓存命中性能,您可以考虑使用数组结构(SoA)。实际上,您只需要交换

中的尺寸
cells[nx*ny][9]

cells[9][nx*ny]

然后你会发现部分副本可以直接用memcpy()完成。

另一方面,我认为你的代码没有严重的错误共享问题。

答案 1 :(得分:0)

当共享高速缓存行被修改(即被写入)时发生错误共享。鉴于此,您可以通过简单地反转操作来大大改善代码的内存访问模式的位置:而不是在cells的每个元素上执行分散操作,在tmp的每个元素上执行收集操作:

tmp[ii*nx + jj].s[0] = cells[ii*nx + jj].s[0];
tmp[ii*nx + jj].s[1] = cells[...].s[1];
...
tmp[ii*nx + jj].s[8] = cells[...].s[8];

这样你就可以线性化每个线程的内存写入模式,使其更加缓存友好并同时减少错误共享。

另请注意,您的代码的性能主要受内存带宽的限制,因此对于大型阵列,您可能无法通过多个线程获得任何加速,除非这样做会提供更多内存带宽,例如:你有一个多套接字系统,每个套接字都有自己的内存控制器,每个线程在不同套接字的内核上执行。您的nx为200和ny 300的测试已经需要至少8,2 MiB的内存,并且几乎不适合大多数桌面处理器的最后一级缓存,但仍然适合大多数服务器的缓存类CPU。更大的2000 x 3000情况肯定是内存限制的。