8

我正在使用 OpenMP,需要使用 fetch-and-add 操作。但是,OpenMP 没有提供适当的指令/调用。我想保留最大的可移植性,因此我不想依赖编译器内在函数。

相反,我正在寻找一种方法来利用 OpenMP 的原子操作来实现这一点,但我已经走到了死胡同。这甚至可以做到吗?注意,以下代码几乎可以满足我的要求:

#pragma omp atomic
x += a

几乎——但不完全是,因为我真的需要x. fetch_and_add应定义为产生与以下相同的结果(仅非锁定):

template <typename T>
T fetch_and_add(volatile T& value, T increment) {
    T old;
    #pragma omp critical
    {
        old = value;
        value += increment;
    }
    return old;
}

(如果我没记错的话,可以询问比较和交换的等效问题,但可以根据另一个问题来实现。)

4

2 回答 2

5

从 openmp 3.1 开始,支持捕获原子更新,您可以捕获旧值或新值。因为无论如何我们都必须从内存中取入值来增加它,所以我们应该能够从一个 CPU 寄存器访问它并将它放入一个线程私有变量中才有意义。

如果您使用 gcc(或 g++),有一个很好的解决方法,查找 atomic builtins:http: //gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Atomic-Builtins.html

它认为英特尔的 C/C++ 编译器也支持此功能,但我没有尝试过。

目前(在实现 openmp 3.1 之前),我在 C++ 中使用了内联包装函数,您可以在其中选择在编译时使用的版本:

template <class T>
inline T my_fetch_add(T *ptr, T val) {
  #ifdef GCC_EXTENSION
  return __sync_fetch_and_add(ptr, val);
  #endif
  #ifdef OPENMP_3_1
  T t;
  #pragma omp atomic capture
  { t = *ptr; *ptr += val; }
  return t;
  #endif
}

更新:我刚刚尝试了英特尔的 C++ 编译器,它目前支持 openmp 3.1(实现了原子捕获)。英特尔提供在 linux 中免费使用其编译器用于非商业目的:

http://software.intel.com/en-us/articles/non-commercial-software-download/

GCC 4.7 将支持 openmp 3.1,当它最终发布时......希望很快 :)

于 2011-10-27T15:24:40.903 回答
2

如果要获取 x 的旧值且 a 未更改,请使用 (xa) 作为旧值:

fetch_and_add(int *x, int a) {
 #pragma omp atomic
 *x += a;

 return (*x-a);
}

更新:这不是一个真正的答案,因为 x 可以在原子之后被另一个线程修改。因此,使用 OMP Pragmas 制作通用的“获取和添加”似乎是不可能的。作为通用,我的意思是操作,可以在任何 OMP 代码的地方轻松使用。

您可以使用omp_*_lock函数来模拟原子:

typedef struct { omp_lock_t 锁;整数值;} atomic_simulated_t;

fetch_and_add(atomic_simulated_t *x, int a)
{
  int ret;
  omp_set_lock(x->lock);
  x->value +=a;
  ret = x->value;
  omp_unset_lock(x->lock);
}

这既丑陋又缓慢(执行 2 个原子操作而不是 1 个)。但是,如果您希望您的代码非常可移植,那么在所有情况下它都不是最快的。

您说“如下(仅非锁定)”。但是“非锁定”操作(使用 CPU 的“LOCK”前缀,或 LL/SC 等)和锁定操作(通过几个原子指令自行实现,忙循环用于短暂等待解锁和操作系统睡眠)有什么区别?长时间等待)?

于 2010-11-17T17:25:20.370 回答