β

常用C++并行技术:MPI, Pthreads, OpenMP简介

DevFighter 1832 阅读
C++

由于技术限制,CPU的发展已经从原来的不停升高频率转为不断增加核数,目前很少见到单核的电脑了。原因好像有:1. 制程越来越小,相邻线路间距太小,在高温下更容易干扰,不能继续提高频率了; 2.一个CPU上的线路越来越长,信号无法在一个时钟周期内传递到整个CPU,CPU需要分块运行,即多核。
本文网址:http://devfighter.com/blog/?p=26
那么我们的程序如果不做更改的话,只能在一个核上跑,无法利用多核的优势。并行程序按数据存储可分两种:分布式内存并行和共享内存并行。

分布式内存并行,就是并行的实例间不共用内存,他们可以运行在一台pc上(多进程),也可以运行在多台pc上(大型服务器),实例间一般通过网络通信。
共享内存并行,指并行的实例共用内存,通常就是多线程程序,每个实例是一个线程,一般通过消息通信。

MPI:Message-Passing Interface,一个可供C, C++和Fortran程序调用的函数库,跨平台,属于分布式内存并行技术,以前一般用于集群技术。
启动方式:通过mpiexec命令启动程序,将编译出的程序、启动进程数量以参数方式传递给mpiexec,不同并行程序通过“通信子”参数进行隔离。
实例ID:每个进程有一个进程ID(通过MPI_Comm_size()获取进程ID,通过MPI_Comm_rank()获得总线程数)
通信方式:MPI_Send, MPI_Recv 发送预定义类型数据或组合数据给指定ID的进程。
发送消息可阻塞也可不阻塞,数据类型明确,数据大小明确,接收ID明确,设置Tag分类。
接收消息是阻塞的,可选利用通配符接收所有发送ID,所有Tag的消息。
发送ID、接收ID都一致的消息保证先发先到,其它情况顺序不保证
部分API函数:
Reduce:对所有实例的数据进行集合操作至目标实例,如:累加和、最大值、累乘积;
AllReduce:在Reduce后将Reduce的结果广播给所有实例;
Bcast:将数据广播给所有实例
Scatter:将数据分块循环分配给所有实例(需要数据能整分,即尾块大小与其它一致)
Gather:将所有实例的数据组装到目标实例

Pthreads:POSIXthreads,可链接到C程序的库(比较底层),只能在支持POSIX的系统使用,属于共享内存并行技术。
启动方式:程序调用API启动多个线程
实例ID:程序负责设定
同步方式:互斥量、信号量、条件变量(可用于所有线程的同步)
部分API函数:
pthread_create:创建线程
pthread_join:等待线程结束(合并线程)
pthread_mutex_lock/unlock:互斥量,类似锁
sem_post/wait:信号量,生产者-消费者
pthread_cond_wait/broadcast:条件变量,广播后同时解锁
pthread_rwlock_rdlock/wrlock/unlock:读写锁

OpenMP:编译器扩展,需要编译器支持OpenMP,属于共享内存并行技术。
启动方式:代码块之前加一行:#pragma omp parallel *** 自动创建线程并行运行代码块
实例ID:每个线程一个ID(通过opm_get_thread_num()获取ID,通过opm_get_num_threads()获取总线程数)
同步方式:临界区、原子操作、锁
部分pragma omp子命令:
parallel:以多线程并行方式运行代码块,num_threads()设置线程数
parallel for:以多线程并行方式运行for循环,要求for运行次数明确,reduction()设置归约操作和归约变量,private/shared设置变量并行作用域,schedule()设置调度方式
critical:临界区,可命名
atomic:对简单语句进行并行保护
barrier:路障,同步所有线程
部分API:
omp_set_lock/unset_lock:锁

MPI适合对计算量需求较大,内存需求也较大的程序,但实例间数据传输的耗时较大。
Pthreads比较底层,需要自己管理线程,需要避免内存数据的访问竞争和循环依赖。虽然使用麻烦,但程序对于并行的任务分配更加自由。
OpenMP比较高层,无需自己管理线程,使用比较省事,但对于能并行的语句块、for循环有严格的要求,需要避免访问冲突和循环依赖。

对于共享内存并行程序,影响效率的一个很隐蔽的原因还有缓存一致性导致的伪共享。例如:变量x与y在内存中处于一个缓存行,CPU的缓存是以“缓存行”为单位缓存数据的,如果线程a频繁的改写x,线程b需要频繁的读y,虽然没有发生访问竞争,但是写x会导致另一个线程的缓存行失效,导致b需要读y时,线程a必须将缓存行保存到内存,线程b再重新从内存加载缓存行,出现对缓存行的伪共享。如果串行,CPU可以按缓存的速度进行数据读写,由于出现了伪共享,并行程序反而只能以内存的速度读写变量,运行效率大打折扣。
因此,共享内存并行程序的设计一定要规划好算法对内存的访问。

当然,并行程序的开发需要开发者考虑很多细节来提升效率,不一样的问题、问题规模、硬件平台都会对运行效率产生影响。
–devfighter

C++
作者:DevFighter
学习,不能停,探索,不能停
原文地址:常用C++并行技术:MPI, Pthreads, OpenMP简介, 感谢原作者分享。

发表评论