并行程序设计

并行

第一章 简介

并行计算:表示一个系统具有多个处理器,所有处理器可以访问共享存储器来交换信息,通信开销低。

分布式计算:每个系统具有自己的存储器,通过网络将系统连通,系统间通过消息传递的方式交换信息,通信开销高。

加速比: S = 1 ( 1 = a + a n ) \large{S = \frac{1}{(1 = a + \frac{a}{n})}} S=(1=a+na)1
a:可并行计算部分占比
n:并行处理节点个数

并行的开销包括:启动进程或线程的成本、共享数据的成本、同步的成本、额外的冗余计算成本。

第二章 并行设计基础

SIMD Single Instruction Multiple-Data
PVP Parallel Vector Processor
MPP Massively Parallel Processor
SMP Symmetric Multi-Processor
COW Cluster of Workstaion
DSM Distributed Shared Memory

共享变量:PVP SMP DSM(分布共享)
消息传递:MPP COW (均为分布非共享)

UMA Uniform Memory Access 均匀共享
NUMA NonUniform Memory Access 非均匀共享
COMA Cache-Only Memory Access 全高速缓存
CC-NUMA Coherent-Cache NonUniform Memory Access 高速缓存一致性
NORMA No Remote Memory Access 私有存储器

甚细粒度:指令级 硬件实施
细粒度: 数据级 编译器实施 共享变量
中粒度: 控制级 程序员实施 共享变量/消息传递
粗粒度: 作业级 操作系统实施 消息传递

并行编程模型
主从模型 Master-Slave 主进程分发和收集数据 从进程计算数据
单程序多数据流 SPMD 并行进程执行相同的代码 处理不同数据
数据流水线 Data-Pipeline 整个任务划分成不同阶段 并行进程负责不同阶段
分治策略 Divide and Conquer 将输入分解为子问题 递归求解子问题

并行程序设计四阶段
划分 Partitioning 划分为小任务
通信 Communication 确定数据交换
组合 Agglomeration 组合为大任务
映射 Mapping 为处理器分配任务

划分
域分解 划分对象是数据 将数据分解为大致相等的小数据片
功能分解 划分对象是计算 将计算划分为不同的任务

划分具有灵活性,避免冗余计算与存储,划分任务比例相当,任务数与问题尺寸成比例,功能划分要合理。

通信
局部通信
全局通信 All-to-All Master-Worker
结构化通信 通信模式相同
非结构化通信 无结构化网络
静态通信 通信伙伴身份不变
动态通信 通信伙伴由数据动态决定
同步通信 接收方和发送方协同操作
异步通信 接收方获取数据无需和发送方协同操作

任务执行要有相当的通信,尽可能局部通信,通信操作可以并行执行,同步任务的计算可以并行执行。

组合
合并小尺寸任务,减少任务数
增加任务的粒度和重复计算,可以减少通信成本;保持映射和扩展的灵活性,可以降低软件工程成本。

增加粒度和重复计算要减少通信成本,保持灵活性和可扩展性,任务数和问题尺寸成比例,保持类似的通信和计算,维持并行执行的机会。

映射
每个任务要映射到具体的处理器上
任务数大于处理器数:考虑负载平衡和任务调度
映射的目标是减少算法的执行时间,并发的任务映射到不同的处理器上,高通信的任务映射到同一个处理器上。
映射是一种权衡,是NP完全问题。

负载平衡
静态的:事先确定 静态分配一般是任务到进程的算术映射
概率的:随机确定 动态分配 Master-Slave 经理雇员模型
动态的:执行期间动态负载
基于域分解的

集中式负载平衡的通信瓶颈,动态负载平衡的调度策略成本

并行设计模型
数据并行模型 SIMD
数据并行的同步是在编译时而不是在运行时完成;单线程;并行操作于聚合数据结构
共享变量模型 PVP SMP DSM
多线程SPMD,MPMD;异步;显式同步;
消息传递模型 MPP COW
MPI和PVM实现;多线程;异步并行;分开的地址空间

第三章 共享内存架构

每个处理器拥有私有存储(缓存)
所有处理器共享内存空间
处理器可以并行进行计算

用PCAM研究问题

临界区
竞争条件 当执行的结果取决于两个或多个事件的执行时间时,就存在竞争条件
临界区 对共享存储区域进行更新的代码段,或会造成竞争条件的代码段

原子性 指事务的不可分割性,一个事务的所有操作要么全部执行,要么全部不执行.
互斥锁 在任何情况下,被标记了互斥锁的代码最多只有一个线程可以执行.
互斥锁降低并行性

同步
路障 同步点,只有所有线程都达到路障,线程才能继续运行下去,否则将阻塞在路障处

第四章 Pthreads

Pthreads是POSIX线程库,支持创建并行环境,同步,隐式通信

启动线程
errcode = pthread_create(&thread_id, &thread_attribute, &thread_fun, &fun_arg);

停止线程
errcode = pthread_join(thread_id, return);

忙等待 Busy-Waiting
线程重复测试某个条件,需要关闭编译器优化

互斥锁
int pthread_mutex_init(pthread_mutex_t* mutex_p, const pthread _mutexattr_t* attr_p);
int pthread_mutex_destroy(pthread_mutex_t* mutex_p) ;
int pthread_mutex_lock(pthread_mutex_t* mutex_p);
int pthread_mutex_unlock(pthread_mutex_t* mutex_p);
互斥锁变量是共享变量

信号量
int sem_init(sem_t* semaphore_p,int shared,unsigned initial_val);
int sem_destroy(sem_t* semaphore_p);
int sem_wait(sem_t* semaphore_p); P操作
int sem_post(sem_t* semaphore_p); V操作

条件变量
int pthread_cond_init(pthread_cond_t* cond_p, const pthread _condattr_t* cond_attr_p);
int pthread_cond_destroy(pthread_cond_t* cond_p) ;
int pthread_cond_signal(pthread_cond_t* cond_p) ;
int pthread_cond_broadcast(pthread_cond_t* cond_p) ;
int pthread_cond_wait(pthread_cond_t* cond_p,pthread_mutex_t* mutex_p) ;

第五章 循环

数据依赖
如果两个存储访问操作访问了同一个存储位置,其中一个操作为写操作,那么这两个操作存在数据依赖.

写后读Read after write 真相关 True dependence
读后写Write after read 反相关 Anti-dependence
写后写Write after write 输出相关 Output dependence
读后读 输入相关

I i I_i Ii为进程i的输入, O i O_i Oi为进程i的输出,如果 I i ∩ O j = I j ∩ O i = O i ∩ O j = ∅ I_i \cap O_j = I_j \cap O_i = O_i \cap O_j = \emptyset IiOj=IjOi=OiOj=,则i和j可以并行.

数据重用 相同数据或相邻数据被多次使用.
数据局部性 被重用的数据被存储在较快的存储器中.

循环体依赖 循环迭代间存在数据依赖
循环无关依赖 循环体内部存在数据依赖

迭代空间
迭代实例 用迭代空间中的坐标来表示
字典序 迭代的串行执行顺序
距离向量 描述一个迭代空间中迭代的依赖关系
方向向量 用来描述循环体依赖
合法的方向向量 [=,=],[=,<],[<,=],[<,<],[<,>]

循环转换
循环交换 Loop Interchange 交换循环的顺序来改变数据的遍历顺序
n维循环的距离向量d大于等于0则可以交换循环 合法的方向向量只有[<,>]不可以交换
循环分块 Loop Tile
重排循环迭代,让有数据重用的迭代相继执行 strip-mine-and-interchange分块法 先增加循环层次 再循环分块
本质是在外层循环探索内层循环时间和空间上的重用
循环展开 Loop Unroll
将多个循环展开在一个循环内
循环融合 Loop Fusion
将多个循环融合在一个循环内

n维循环中,距离向量 D = [ d 1 , d 2 , d 3 , . . . , d n ] D=[d_1,d_2,d_3,...,d_n] D=[d1,d2,d3,...,dn],如果 d i d_i di是第一个非0的值,那么i层存在循环体依赖. [ d 1 , d 2 , d 3 , . . . , d j − 1 ] > 0 [d_1,d_2,d_3,...,d_{j-1}]>0 [d1,d2,d3,...,dj1]>0或者 [ d 1 , d 2 , d 3 , . . . , d j ] = 0 [d_1,d_2,d_3,...,d_j]=0 [d1,d2,d3,...,dj]=0,j层才可以并行.

第六章 OpenMP

由三个API构成:编译时指令,运行时库函数,环境变量

#pragma omp parallel private(nthreads, tid)

private(list) 线程私有变量
shared(list) 线程共享变量
default(shared) 指定并行区域内变量是shared
firstprivate(list) 进入并行区域前初始化为同名变量
lastprivate(list) 退出并行区域前赋值为同名变量
copyin(list) 拷贝复制给并行区域各个线程的私有变量
copyprivate(list) 将单个线程的私有变量广播给其他线程的私有变量
reduction(reduction-identifier : list) 对变量进行规约操作,list中为共享变量

#pragma omp parallel if(1) private(nthreads, tid)

if子句 如果表达式为真才会并行执行 否则串行执行
num_threads(int) 用来设置并行区域有多少线程数

工作共享结构 Working Constructs
将工作共享结构封装在一个并行区域中,结构将所包含的代码划分给线程组中的成员来完成.
进入工作共享结构入口没有屏障 出口有隐藏屏障

#pragma omp for schedule(static,chunk) nowait

for指令指定循环语句必须并行执行,假设已经启动了并行区域,否则将串行执行.
schedule子句 描述循环迭代如何进行划分 分为static dynamic(块大小不变) guided(块大小随剩余大小减小而减小) runtime auto

ordered 指定循环迭代需要串行执行 只能用在for中
nowait 忽略并行执行区域隐藏的barrier同步等待
collapse 将循环展开并行执行

不能是while循环 块大小必须为整数次迭代 schedule ordered collapse三者只能出现一次

#pragma omp sections
{
#pragma omp section

#pragma omp section

}
用section进行划分 不同的section由不同线程执行 可以使用private reduction和nowait
sections末尾有隐藏的barrier

#pragma omp single
single指令 用于串行执行一个代码块

简化语句
#pragma omp parallel for
#pragma omp parallel sections

同步指令
master 只有主线程执行这个代码块 其他线程跳过 没有barrier
critical 只能串行执行
barrier 指定线程组需要在此指令处同步
atomic 原子地执行指令 子句包括read write update(默认) capture(获得变量的指定位置的原始值或最终值)
flush 表明程序点处需要内存更新 nowait会使其失效
threadprivate 指定全局变量被所有线程各自产生一个副本

运行时函数
omp_get_thread_num() 获取rank
omp_get_num_threads() 获取size
其他
omp_set_num_threads(int) 设置线程数量
omp_get_max_threads() 获得获取size函数返回的最大值
omp_get_num_procs 返回程序可用处理器数
互斥锁
void omp_init_lock(omp_lock_t *lock);
void omp_destroy_lock(omp_lock_t *lock)
void omp_set_lock(omp_lock_t *lock)
void omp_unset_lock(omp_lock_t *lock)
环境变量
setenv OMP_SCHEDULE “dynamic, 4” 设置schedule子句runtime怎么处理
setenv OMP_NUM_THREADS 8 设置最大可执行线程数
setenv OMP_DYNAMIC TRUE 设置线程数是否可以动态调整

第七章 MPI

每个处理器有私有内存,处理器只能访问自己的内存,运行在处理器内存对上的程序成为进程,进程间采取消息传递的方式显示进行通信.

MPI Message Passing Interface

int MPI_Init( int *argc, char * * * argv ) 初始化MPI环境
int MPI_Finalize(void) 从MPI环境退出
int MPI_Comm_rank(MPI_Comm comm, int *rank) 获取进程的rank
int MPI_Comm_size(MPI_Comm comm, int *size) 获取size
int MPI_Send(void *buf, int count, MPI_Datatype dataytpe, int dest, int tag, MPI_Comm comm) 消息发送
int MPI_Recv(void *buf, int count, MPI_Datatype datatyepe,int source, int tag, MPI_Comm comm, MPI_Status *status) 消息接收

消息缓冲包括<起始地址, 个数, 类型>
消息信封包括<源/目标进程, 标签, 通信域>

MPI_INT MPI_BYTE MPI_PACKED(传递空间不连续的数据项)
MPI_Pack(buf, count, dtype, packbuf, packsize, packpos,communicator) 打包
MPI_Unpack(packbuf, packsize, packpos, buf, count, dtype, communicatior) 拆包

派生数据类型 可以用类型图来描述 是一系列<基类型 偏移量>的二元组
先定义MPI_Data_type newtype,再调用构造函数如MPI_Type_vector(count, blocklength, stride, oldtype, &newtype)来定义派生类型,最后需要调用MPI_Type_commit()获得系统的确认.

MPI_Type_struct(
count, //成员数
array_of_blocklengths, //成员块长度数组
array_of_displacements,//成员偏移数组
array_of_types, //成员类型数组
newtype // 新类型
)

消息标签 通配符MPI_ANY_TAG
通信域 进程组和通信的上下文组合形成了通信域,所有进程的集合是MPI_COMM_WORLD

MPI_Comm_dup(域, &MyWorld) 复制通信域 包含相同进程组
MPI_Comm_split(MyWorld, split, rank , &splitWorld) 每个进程按照不同的split值划分到不同的分割通信域中,编号被定义为rank

通信模式
同步 只有正确的接收方式已经启动,发送过程才会返回
缓冲 发送不管接收方式是否启动都可以进行
标准 由MPI实现来决定,要么同步要么缓冲
就绪 只有正确的接收方式已经开始,发送过程才会开始
通信机制
阻塞 通信操作已经完成/调用的缓冲区可用
非阻塞 通过MPI_Wait(send_handle/recv_handle, status)(阻塞)/MPI_Test(handle, flag=true/false, status)(非阻塞)来检测是否完成

MPI_Sendrecv(
sendbuf, sendcount, sendtype, dest, sendtag,
//以上为消息发送的描述
recvbuf, recvcount, recvtype, source, recvtag,
// 以上为消息接收的描述
comm, status) 同时发送和接收,可以避免死锁

一对多进程 负责发送的为Root
多对一进程 负责接收的为Root
多对多进程

MPI_Bcast(Address, Count, Datatype, Root, Comm) 广播给通信域所有进程
MPI_Gather(SendAddress, SendCount, SendDatatype,RecvAddress, RecvCount, RecvDatatype, Root, Comm) 从通信域中包括自己的进程收集信息
MPI_Scatter(SendAddress, SendCount, SendDatatype,RecvAddress, RecvCount, RecvDatatype, Root, Comm) 将自己缓冲中的内容一一发给对应rank的进程 与Gather恰好相反
MPI_Allgather(SendAddress, SendCount, SendDatatype, RecvAddress, RecvCount, RecvDatatype, Comm) 全局收集 每一个进程都收集所有进程的内容
MPI_Alltoall(SendAddress, SendCount, SendDatatype, RecvAddress, RecvCount, RecvDatatype, Comm) 每个进程作为Root进行了一次Scatter操作
MPI_Barrier(Comm) 路障同步
MPI_Reduce(SendAddress, RecvAddress, Count, Datatype, Op, Root, Comm) 规约操作完成计算
MPI_MAX MPI_MIN MPI_SUM求和 MPI_PROD求积
MPI_MAXLOC 最大值且相应位置 MPI_MINLOC 最小值且相应位置

int MPI_Op_create(
//用户自定义归约函数
MPI_User_function *function,
// if (commute==true) Op是可交换且可结合
// else 按进程号升序进行Op操作
int commute,
MPI_Op *op)

MPI_scan(SendAddress, RecvAddress, Count, Datatype, Op, Comm) 特殊的归约操作,进程对于其前面的进程进行归约操作

群集通信是标准 阻塞的通信模式,一个进程一旦结束了它所参与的群集操作就会从群集函数中返回,但是并不保证其他进程执行该群集函数已经完成.
群集函数中消息没有消息标签.

第八章 树形搜索

在搜索可行解时构造一棵树,树的叶子节点对应一个回路

  • 13
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值