对OpenMP reduction子句中的变量执行原子操作
atomic operations on a variable which is in OpenMP reduction clause
我有以下一段代码。它的想法很简单。我的完整程序中有数十亿个事件,我需要计算其中一些不使用long-int类型的事件。因此,我必须使用2个int数字HIT和COUNT作为1个intnumber的开头,因为会有一个1int变量的溢出(非常大的循环计数)。
#include <fstream>
#include <cstring>
#include <cmath>
#include <random>
#include <limits>
#include <chrono>
using namespace std;
int N=1000000000;
long int K=20*N;
int HIT=0;
int COUNT=0;
long int MAX=std::numeric_limits<int>::max();
int main(int argc, char **argv)
{
auto begin=std::chrono::steady_clock::now();
for(long int i=0; i<K; ++i)
{
++HIT;
if(HIT == MAX)
{
++COUNT;
HIT=0;
cout<<"COUNT="<<COUNT<<endl;
}
}
auto end=std::chrono::steady_clock::now();
cout<<"HIT="<<HIT<<endl;
cout<<"COUNT="<<COUNT<<endl;
const long int Total = HIT+COUNT*MAX;
cout<<"Total="<<Total<<" MAX="<<MAX<<endl;
if(Total==K) cout<<"Total == K"<<endl;
else cout<<"Total != K"<<endl;
auto elapsed_ms=std::chrono::duration_cast<std::chrono::milliseconds>(end-begin);
std::cout<<"time="<<elapsed_ms.count()<<" ms"<<std::endl;
return 0;
}
该代码在1个线程中正常工作,并给出以下输出:
COUNT=1
COUNT=2
COUNT=3
COUNT=4
COUNT=5
COUNT=6
COUNT=7
COUNT=8
COUNT=9
HIT=672647177
COUNT=9
Total=20000000000 MAX=2147483647
Total == K
time=30971 ms
如果可能的话,我需要让它使用OpenMP并行工作,而不使用互斥或与编译器实现连接的一些函数。但当我将其修改为:
#pragma omp parallel for simd reduction(+:HIT,COUNT)
for(long int i=0; i<K; ++i)
输出如下:
HIT=20000000000
COUNT=0
Total=20000000000 MAX=2147483647
Total == K
time=2771 ms
最后,当我将代码修改为:
#pragma omp parallel for simd reduction(+:HIT,COUNT)
for(long int i=0; i<K; ++i)
{
++HIT;
if(HIT == MAX)
{
++COUNT;
#pragma omp atomic write
HIT=0;
cout<<"COUNT="<<COUNT<<endl;
}
}
输出为:
COUNT=1
COUNT=1
COUNT=1
COUNT=1
COUNT=1
COUNT=1
COUNT=1
COUNT=1
HIT=2820130824
COUNT=8
Total=20000000000 MAX=2147483647
Total == K
time=4232 ms
有人能向我解释一下发生了什么以及为什么输出如此不同吗?
我需要使用OpenMP使代码正确地并行工作,那么如何正确地做到这一点呢?
是否
#pragma omp atomic write
正确还是应该写
#pragma omp atomic update?
是否可以对OpenMPreduction子句中已经存在的值编写原子操作?
使用英特尔C++2019编译器。
g++不允许在中使用simd
#pragma omp parallel for simd reduction(+:HIT,COUNT)
如果要删除simd,则代码使用g++时工作不正确。
简单的+
约简对两个不完全独立求和的整数不起作用,但自从OpenMP 4.0以来,您可以声明自己的约简。您所需要做的就是抽象class
(或struct
)中计数器的两个部分,并定义一个对这些对象求和的函数。在下面的例子中,使用了一个重载的复合赋值运算符(+=
):
#include<限制>#包括<iostream>#包括<omp.h>使用命名空间std;const long int MAX=std::numeric_limits<int>:max();const long int K=MAX+20L;类large_count{int计数,命中;公共:large_count():计数(0),命中(0){}//前缀增量运算符large_count&运算符++(){命中++;if(hit==MAX){hit=0;计数++;}return*this;}//复合赋值运算符large_count&运算符+=(const large_count和other){count+=其他计数;long int sum_hit=(long)hit+other.hit;如果(sum_hit>=MAX){计数++;hit=sum_hit-MAX;}其他的hit=sum_hit;return*this;}long total()const{return hit+count*MAX;}};#pragma omp声明减少(large_sum:large_count:omp_out+=omp_in)int main(){large_count cnt;双t=-omp_get_wtime();#用于归约的pragma omp并行(large_sum:cnt)for(long int i=0;i<K;i++)++cnt;t+=omp_get_wtime();cout<lt;(cnt.total()==K"YES":"否")<lt;endl;cout<lt;t<lt"s"<lt;endl;}
自定义减少使用:声明
#pragma omp declare reduction (large_sum : large_count : omp_out += omp_in)
声明有三个部分:
large_sum
-这是自定义缩减操作的名称large_count
-这是还原操作的类型omp_out += omp_in
-这是组合器表达式。CCD_ 8和CCD_ 9是由OpenMP运行时提供的特殊伪变量。它们都属于large_count
型。组合器表达式必须组合这两个值并更新omp_out
的值
样本输出:
$ g++ --version
g++ (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0
...
$ g++ -std=c++11 -fopenmp -o cnt cnt.cc
$ OMP_NUM_THREADS=1 ./cnt
YES
9.39628 s
$ OMP_NUM_THREADS=3 ./cnt
YES
3.79765 s
问题源于每个线程都有自己的HIT
和COUNT
副本。许多线程将以HIT
中的大值结束。当循环结束时,由于OpenMP reduce子句,这些被聚合,导致多个";溢出";CCD_ 15。
对所示代码的OpenMP实现的简单修复是包括
COUNT += HIT / MAX;
HIT %= MAX;
循环结束后。
原子写入指令是转移注意力。它改变了循环的时间,导致更多的线程达到溢出限制。
从您的问题描述中,听起来代码中实际的HIT
是int
,而不是long int
。这更难解决,因为无法使用上面的简单除法计算多个溢出,因为您没有完全计算所有溢出的精度。您还应该考虑使用unsigned
而不是有符号的int
类型,因为这可能会延迟溢出问题,并且在发生溢出时,可以避免有符号值溢出时出现"未定义行为"。
可能的解决方案包括:
- 使用单个
MAX
和COUNT
变量以及受互斥保护的代码块来对这两个值进行非原子递增 - 使用单个
MAX
和COUNT
变量(声明为std::atomic
)以及fetch_add
(或可能的exchange
)来处理更新。如果使用unsigned
类型,则可以让MAX
滚动到0,并在发生滚动时更新COUNT
- 将
MAX
更改为较小的数字,以使(nThreads * MAX
)不超过数字限制
- 执行函数时导致崩溃的变量
- 是否可以依赖函数范围的静态变量来执行程序关闭期间调用的方法?
- 对OpenMP reduction子句中的变量执行原子操作
- 如果包含映射的静态库与可执行文件和动态库链接,静态映射(变量)是否会被多次释放?
- 子线程中的条件变量等待停止主线程中的执行
- 如果函数包含静态变量,为什么编译器不执行内联?
- 提供变量作为 MATLAB 系统命令的输入参数,以便C++可执行文件
- 为什么变量的打印地址在每次执行时都会打印随机值,即使它是 C 中的逻辑地址?
- 我如何将一个变量与另一个变量进行比较,例如我想如果(var1 > var2 x 1),然后执行此 c++
- 在 MinGW 和 MinGW-64 上执行命令后变量为空?
- 程序的执行在 C++ 中输入 char 变量后结束
- 即使变量为 false,'If'也会执行
- make_shared是否对每个成员变量执行默认初始化(零初始化)
- 在链接的程序集文件中,我想从 c++ 调用代码访问变量.是否可以在不触发访问冲突的情况下执行此操作?
- 我可以使用相同的qsqlquery变量执行多个语句
- 在C++中对浮点变量执行算术时,是否始终需要使用浮点数文字
- 如何为指针类变量执行 setter 和 getter 函数
- 从字符串变量执行c++
- 对共享变量执行多个原子操作
- 告诉子类对超类的受保护变量执行某些操作是否是一种好的做法(也许是一些已知的设计模式?)?