C++ 使用OpenMP原子操作获取和添加
我正在使用OpenMP,需要使用fetch和add操作。但是,OpenMP没有提供适当的指令/调用。我希望保持最大的可移植性,因此我不想依赖于编译器内部函数 相反,我正在寻找一种方法来利用OpenMP的原子操作来实现这一点,但我遇到了死胡同。这能做到吗?注意,以下代码几乎满足了我的要求:C++ 使用OpenMP原子操作获取和添加,c++,atomic,openmp,compare-and-swap,C++,Atomic,Openmp,Compare And Swap,我正在使用OpenMP,需要使用fetch和add操作。但是,OpenMP没有提供适当的指令/调用。我希望保持最大的可移植性,因此我不想依赖于编译器内部函数 相反,我正在寻找一种方法来利用OpenMP的原子操作来实现这一点,但我遇到了死胡同。这能做到吗?注意,以下代码几乎满足了我的要求: #pragma omp atomic x += a 差不多–但不完全一样,因为我确实需要旧值x提取和添加以产生与以下相同的结果(仅非锁定): 模板 T取_和_加(易失性T&值,T增量){ T老; #pragm
#pragma omp atomic
x += a
差不多–但不完全一样,因为我确实需要旧值x
<应定义代码>提取和添加以产生与以下相同的结果(仅非锁定):
模板
T取_和_加(易失性T&值,T增量){
T老;
#pragma-omp-critical
{
旧=价值;
值+=增量;
}
返老还童;
}
(如果我没有弄错的话,可以问一个等价的问题进行比较和交换,但一个可以用另一个来实现。)如果您想得到x的旧值,并且a没有改变,请使用(x-a)作为旧值:
fetch_and_add(int *x, int a) {
#pragma omp atomic
*x += a;
return (*x-a);
}
更新:这并不是一个真正的答案,因为x可以在原子后被另一个线程修改。
因此,似乎不可能使用OMP Pragmas实现通用的“获取和添加”。作为通用的,我指的是操作,它可以从OMP代码的任何位置轻松使用
您可以使用omp\u*\ u lock
函数来模拟原子:
typedef结构{omp_lock_t lock;int value;}原子结构
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的“锁定”前缀,或LL/SC或等)和锁定操作(使用多个原子指令、短时间等待解锁的忙循环和长时间等待的操作系统睡眠)之间有什么区别呢?从openmp 3.1开始,支持捕获原子更新,您可以捕获旧值或新值。因为我们必须从内存中引入值来增加它,所以我们应该能够从CPU寄存器访问它并将其放入线程私有变量中才有意义 如果您使用的是gcc(或g++),可以查找原子内置: 它认为英特尔的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
}
模板
内联T my_fetch_add(T*ptr,T val){
#ifdef GCC_扩展
返回、同步、获取和添加(ptr、val);
#恩迪夫
#ifdef OPENMP_3_1
T;
#pragma-omp原子捕获
{t=*ptr;*ptr+=val;}
返回t;
#恩迪夫
}
更新:我刚试用了英特尔的C++编译器,它目前支持OpenMP 3.1(实现原子捕获)。英特尔为非商业目的免费使用其linux编译器:
GCC 4.7最终发布时将支持openmp 3.1。。。希望很快:)对于cas,openmp支持条件原子的一种变体,但只支持fortran语言。它是一个最小值和最大值;它们是有条件的。可以用来实现CAS操作的一个子集。@Konrad Rudolph,我也是,因为我需要1周的时间才能得到这个:)。另外,我所需要的步骤是学习不同平台上的LL/SC操作。@osgx:这实际上不是问题,因为您可以事先将类型强制为适当位大小的整数。当然,这取决于UB和平台,但有了适当的防护措施,这是一种非常可靠的技术。该死,这个答案也不正确:在原子更新和最后一行之间,另一个线程当然可能会修改
x
@康拉德·鲁道夫,是的,第一次尝试是错误的。更新了答案。我已经求助于使用GCC内置,但当然这对互操作性来说是可怕的。感谢OpenMP 3.1指针。不幸的是,由于VC++目前甚至不支持OpenMP 3,目前这是一个相当理论性的问题。仅适用于完整版本:它应该是\ifdef\uu GNUC\uu
<代码>#elif定义(_OPENMP)和_OPENMP>=201107(适用于OPENMP 3.1)#else#error“需要gcc或OpenMP>=3.1”#endif
。谢谢只是说,atomic
并不是它的名字所承诺的那样,因为任何线程(在任何其他线程上)的atomic
修改了内存后,都需要重新缓存。所以频繁和重复<代码>原子< /代码>可能会杀死你的性能(更好地使用锁和缓冲赛跑写)。@沃尔特,这也是我发现的经验:无锁算法执行等同于使用锁的等效算法。而无锁算法使用的同步要复杂得多——不是性能方面,而是逻辑方面(以及引入bug的机会)。
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
}