装配说明,以取代openmp关键区域

时间:2012-09-11 09:37:30

标签: assembly openmp critical-section

我有一个由openmp任务处理的元素数组。任务可能会在数组末尾添加新元素。当然,这些元素也必须处理,并可能产生新的项目。目前我正在使用此代码

int p;
#pragma omp critical
{
    p=l.n++;
}

这只是在阵列末尾保留一个位置。 l的类型是

struct list
{
    int n;
    double *e;
}

p将用作存储新元素的位置的索引。 我想知道是否有办法在不使用关键区域的情况下执行此操作。是否有一个汇编指令复制一个值,然后原子地增加原始值?

代码将在nehalem cpu上执行,无需担心旧机器

3 个答案:

答案 0 :(得分:6)

#pragma omp atomic capture
p = l.n++;

如果硬件支持,则应该在捕获值时使用原子增量。

在此问题中详细了解#pragma omp atomicopenMP, atomic vs critical?

这是Intel's documentation for #pragma omp atomic

我尝试使用gcc -fopenmp -m32 -O2 -S编译最小示例:

int i, j;
void foo (void)
{
  #pragma omp atomic capture
  i = j++;
}

我得到的是一个简单的原子'fetch and add',这就是我们想要的:

movl $1, %eax       # eax = 1
lock xaddl %eax, j  # atomic {swap (eax,j); j = eax + j;}
movl %eax, i        # i = eax
ret

答案 1 :(得分:1)

是的,x86上有一些可能的选择。

XADD r/m, r

该指令以原子方式将第二个操作数(r)添加到第一个操作数(r / m),并使用第一个操作数的原始值(r / m)加载第二个操作数(r)。

要使用它,你需要加载第二个操作数和增量(我猜测,1,这里),第一个操作数应该是增加的内存位置。

此指令必须以LOCK前缀开头(它将使其成为原子)。

Microsoft Visual C ++中的InterlockedAdd()函数执行此操作,并且AFAIR使用XADD(如果可用)(从i80486开始提供)。

另一种方法是使用带有CMPXCHG指令的循环...

伪代码:

while (true)
{
  int oldValue = l.n;
  int newValue = oldValue + 1;
  if (CAS(&l.n, newValue, oldValue) == oldValue)
    break;
}

CAS()代表Compare And Swap(并发编程中的常用术语),是一个尝试用新值原子替换内存中值的函数。当被替换的值等于最后提供的参数oldValue时,替换成功。否则就失败了。 CAS返回内存中的原始值,让我们知道替换是否成功(我们将返回值与oldValue进行比较)。失败(返回的原始值与oldValue不同)表示在读取oldValue和我们尝试用newValue替换它的那一刻之间,另一个线程更改了内存中的值。在这种情况下,我们只需重试整个过程。

CMPXCHG指令是x86 CAS

在Microsoft Visual C ++ InterlockedCompareExchange()中使用CMPXCHG来实现CAS

如果XADD不可用,则使用InterlockedAdd() / CAS / CMPXCHG实施InterlockedCompareExchange()

在其他一些CPU上可能还有其他可能性。有些允许原子执行一些相邻的指令。

答案 2 :(得分:0)

这实际上只是一个返回结果的原子增量,如下所示:

mov p, 1  ; p must be a register
lock xadd [l.n], p

现在你知道了。我认为没有理由实际使用它,但有一些方法可以不使用汇编代码。