【问题标题】:Assembly instruction to replace openmp critical region替换openmp临界区的汇编指令
【发布时间】:2012-09-11 09:37:30
【问题描述】:

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

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

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

struct list
{
    int n;
    double *e;
}

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

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

【问题讨论】:

    标签: assembly openmp critical-section


    【解决方案1】:
    #pragma omp atomic capture
    p = l.n++;
    

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

    在这个问题中阅读更多关于#pragma omp atomic的信息:openMP, 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++;
    }
    

    我得到的是一个简单的原子“获取和添加”,这正是我们想要的:

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

    【讨论】:

    • 不起作用。编译器 (icc) 说错误:原子表达式中的非法操作。这就是为什么我一直在寻找组装说明
    • @Patrik - 显然,默认情况下#pragma omp atomic 只能进行原子更新。你需要的是#pragma omp atomic capture。我已经编辑了答案以反映这一点。
    • @Patrik - 我很想知道capture 子句是否修复了 ICC 中的错误。至少我现在拥有的 GCC 副本已经足够老了,它没有实现该子句,所以我无法自己验证它。
    • 确实如此。也许这就是我正在寻找的。我想避免锁定
    • @Patrik - 我刚刚检查了 GCC。似乎做对了。它会生成我们想要的“原子获取和添加”。 See this.
    【解决方案2】:

    是的,在 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 不可用,则使用CAS/CMPXCHG/InterlockedCompareExchange() 实现InterlockedAdd()

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

    【讨论】:

      【解决方案3】:

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

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

      现在你知道了。我认为没有理由实际使用它,但有一些方法可以在不求助于汇编代码的情况下做到这一点。

      【讨论】:

      • 我认为汇编是最自然的选择,因为每条指令都是原子的
      • @Patrik 好吧,是的,在某处必须使用指令 - 但一切都是如此。不过像往常一样,它可以被抽象化。此外,并非所有指令都是原子指令。
      猜你喜欢
      • 2016-05-12
      • 2014-12-08
      • 1970-01-01
      • 2015-12-26
      • 2011-03-30
      • 1970-01-01
      • 1970-01-01
      • 2011-10-27
      • 2016-01-23
      相关资源
      最近更新 更多