在 OpenMP 中对结构 (x,y,z) 变量使用原子操作

Using atomic operation in OpenMP for struct (x,y,z) variable

我正在用 C++ 开发 OpenMP 代码(编译器是 g++ 4.8.2)。在我的部分代码中,我需要对结构数据执行原子添加。 strcut 定义为:

struct real3 
{
  float x;
  float y;
  float z;
};    

我为它定义加法运算符如下:

inline real3 operator+(real3 a, real3 b)
{ 
  return make_real3(a.x + b.x, a.y + b.y, a.z + b.z);
}

我在我的代码的所有部分都使用了这个 strcut。在我的程序的一部分中,我需要以原子方式执行添加操作:

real3 * m_cforce;
real3 fn, ft;
int i;
/*
 . . . .  some code is here 

*/

#pragma omp atomic
m_cforce[i] = m_cforce[i] + (fn + ft);

编译不接受 struct real3 作为原子添加的操作数。一种解决方案是改用以下代码:

#pragma omp atomic
m_cforce[i].x = m_cforce[i].x + (fn + ft).x;
#pragma omp atomic
m_cforce[i].y = m_cforce[i].y + (fn + ft).y;
#pragma omp atomic
m_cforce[i].z = m_cforce[i].z + (fn + ft).z;

这样一来,我就多用了3倍的atomics,对我来说会耗费更多的时间。有没有什么方法可以让我以较低的计算开销来执行此操作?

OpenMP 原子需要对标量值(简单类型)进行操作。原子旨在由运行时映射到内核甚至指令级原子。在不了解你的问题的情况下,很难给出一个答案,但是对于这类事情的一些常用建议:

  • 使用线程局部变量(如果可能)。如果只有一个线程可以写入它,您可以避免大部分计算的原子操作,然后在最后减少它们。
  • 您上面的 3 原子方法将起作用,但它确实允许多个线程添加到可能交错的同一个 real3。由于浮点不是关联的,这可能会导致更多不确定的结果。总的来说,这是一个不错的选择。
  • 为每个共享的 real3 使用一个 OpenMP 锁。使用 critical 就像在包含的代码段周围使用单个锁。如果你使用每个 real3 的锁,只要它们接触不同的 real3,它们就可以并行操作。 OpenMPs 锁不是最快的,但它们应该比关键锁快。

@user2548418:

这是多体模拟,其中所有物体(范围从 0 到 N-1)相互交互。在第一步中,进行搜索以确定成对交互。然后有一个遍历所有物体的循环来计算相互作用(这是一个平行部分,平行于计数器 i )。简单地说,每个物体 (i) 可以与系统中的 0 到 20 个物体 (j) 交互。当我计算相互作用力时,我应该将这些力添加到每个物体的总力​​中(i 和 j) 我上面演示的代码是执行此添加的部分,因此如果您希望完成此部分,请考虑以下内容:

#pragma omp atomic
m_cforce[i].x = m_cforce[i].x + (fn + ft).x;
#pragma omp atomic
m_cforce[i].y = m_cforce[i].y + (fn + ft).y;
#pragma omp atomic
m_cforce[i].z = m_cforce[i].z + (fn + ft).z;

#pragma omp atomic
m_cforce[j].x = m_cforce[j].x - (fn + ft).x;
#pragma omp atomic
m_cforce[j].y = m_cforce[j].y - (fn + ft).y;
#pragma omp atomic
m_cforce[j].z = m_cforce[j].z - (fn + ft).z;

因为每个线程要修改两个不同的内存位置:一个在 i 位置,对应于迭代计数器,另一个在 j 位置,它是随机的,可以是 0 到 N-1 之间的任何数字。这种依赖性不允许我为每个线程考虑一个私有标量 real3。存在一种解决方案。我为每个线程分配一个 real3[N] 向量,然后在 "parallel for" 末尾使用归约和来计算每个物体上的总力。这可能具有挑战性,因为它会降低捕获效率(原子也会这样做)。所以,我应该将此解决方案与使用原子进行比较。

我知道浮点运算不是关联的。但这对我来说不是限制性的,因为力的顺序差别不大,当操作 a+ (b+c) 变为 (a+b) + c 时,错误率非常低。

我不会在代码中使用锁,否则第一个和第二个解决方案的性能会很差。