我正在使用OpenMP,需要使用fetch-and-add操作。但是,OpenMP不提供适当的指令/调用。我想保留最大的可移植性,因此我不想依赖编译器内在函数。
相反,我正在寻找一种方法来利用OpenMP的原子操作来实现这一点,但我已经走到了尽头。甚至可以这样做吗? N.B.,以下代码几乎做了我想要的事情:
#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;
}
(对于比较和交换,可以提出一个等价的问题,但如果我没有弄错的话,可以用另一个来实现。)
答案 0 :(得分:4)
从openmp 3.1开始,支持捕获原子更新,您可以捕获旧值或新值。因为我们必须从内存中带来值来增加它,所以我们应该能够从一个CPU寄存器访问它并将它放入一个线程私有变量中才有意义。
如果您正在使用gcc(或g ++),请查看原子内置函数,这是一个很好的解决方法: 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,当它最终发布时......希望很快:)
答案 1 :(得分:2)
如果你想得到x的旧值而a没有改变,请使用(x-a)作为旧值:
fetch_and_add(int *x, int a) {
#pragma omp atomic
*x += a;
return (*x-a);
}
更新:这不是一个真正的答案,因为x可以在原子之后被另一个线程修改。 因此,使用OMP Pragma进行通用的“获取和添加”似乎是不可能的。通用我的意思是操作,可以从OMP代码的任何地方轻松使用。
您可以使用omp_*_lock
函数来模拟原子:
typedef struct {omp_lock_t lock; int value;} 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等)与锁定操作(使用多个原子指令实现,忙碌循环,等待解锁和操作系统休眠等)之间有什么区别?等待很长时间)?