赞
踩
进程:是资源分配的最小单位。是并发执行的程序在执行过程中分配和管理资源的基本单位,是一个动态概念,竞争计算机系统资源的基本单位。(资源分配)
线程:是CPU调度的最小单位。是进程的一个执行单元,是进程内可调度实体。比进程更小的独立运行的基本单位。线程也被称为轻量级进程。(执行体)
openmp: 线程级(并行粒度)、共享存储、隐式(数据分配方式)、可扩展性差。
MPI: 进程级、分布式存储、显式、可扩展性好。
openmp优点:
openmp缺点:
MPI优点:
MPI缺点:
并行Hello world
#include <omp.h> #include <stdio.h> #include <stdlib.h> int main() { int nthreads, tid; /* Fork a team of threads giving them their own copies of variables */ #pragma omp parallel private(nthreads, tid) { /* Obtain thread number */ tid = omp_get_thread_num(); printf("Hello World from thread = %d\n", tid); /* Only master thread does this */ if (tid == 0) { nthreads = omp_get_num_threads(); printf("Number of threads = %d\n", nthreads); } } /* All threads join master thread and disband */ return 0; }
gcc test.cpp -o test -fopenmp -lstdc++
并行区域构造:
#pragma omp parallel [clause ...] newline
if (scalar_expression)
private (list)
shared (list)
default(shared | none)
firstprivate (list)
reduction (operator:list)
copyin (list)
num_threads (integer-expression)
structured_block
决定线程数的因素有多个,它们的优先级如下:
指令(directive):
指令 | 作用 |
---|---|
atomic | 内存位置将会原子更新 |
barrier | 线程在此等待,直到所有的线程都运行到此barrier。用来同步所有线程。 |
critical | 其后的代码块为临界区,任意时刻只能被一个线程运行。 |
flush | 所有线程对所有共享对象具有相同的内存视图 |
for | 用在for循环之前,把for循环并行化由多个线程执行。循环变量只能是整型 |
master | 指定由主线程来运行接下来的程序。 |
ordered | 指定在接下来的代码块中,被并行化的 for循环将依序运行(sequential loop) |
parallel | 代表接下来的代码块将被多个线程并行各执行一遍。 |
sections | 将接下来的代码块包含于将被并行执行的section块。 |
single | 之后的程序将只会在一个线程(未必是主线程)中被执行,不会被并行执行。 |
threadprivate | 指定一个变量是线程局部存储(thread local storage) |
从句(clause):
指令 | 作用 |
---|---|
copyin | 让threadprivate的变量的值和主线程的值相同。 |
copyprivate | 不同线程中的变量在所有线程中共享。 |
default | Specifies the behavior of unscoped variables in a parallel region. |
firstprivate | 对于线程局部存储的变量,其初值是进入并行区之前的值。 |
if | 判断条件,可用来决定是否要并行化。 |
lastprivate | 在一个循环并行执行结束后,指定变量的值为循环体在顺序最后一次执行时获取的值,或者#pragma sections在中,按文本顺序最后一个section中执行获取的值。 |
nowait | 忽略barrier的同步等待。 |
num_threads | 设置线程数量的数量。默认值为当前计算机硬件支持的最大并发数。一般就是CPU的内核数目。超线程被操作系统视为独立的CPU内核。 |
ordered | 使用于 for,可以在将循环并行化的时候,将程序中有标记 directive ordered 的部分依序运行。 |
private | 指定变量为线程局部存储。 |
reduction | Specifies that one or more variables that are private to each thread are the subject of a reduction operation at the end of the parallel region.reduction子句对列表中的每个变量执行简化操作。为每个线程创建并初始化每个列表变量的私有副本。在缩减结束时,reduce变量应用于共享变量的所有私有副本,最终结果将写入全局共享变量。 |
schedule | 设置for循环的并行化方法;有 dynamic、guided、runtime、static 四种方法。shared 指定变量为所有线程共享。 |
schedule(static, chunk_size) | 把chunk_size数目的循环体的执行,静态依序指定给各线程。 |
schedule(dynamic, chunk_size) | 把循环体的执行按照chunk_size(缺省值为1)分为若干组(即chunk),每个等待的线程获得当前一组去执行,执行完后重新等待分配新的组。 |
schedule(guided, chunk_size) | 把循环体的执行分组,分配给等待执行的线程。最初的组中的循环体执行数目较大,然后逐渐按指数方式下降到chunk_size。 |
schedule(runtime) | 循环的并行化方式不在编译时静态确定,而是推迟到程序执行时动态地根据环境变量OMP_SCHEDULE 来决定要使用的方法。 |
shared | 指定变量为所有线程共享。 |
openmp的库函数
指令 | 作用 |
---|---|
void omp_set_num_threads(int _Num_threads) | 在后续并行区域设置线程数,此调用只影响调用线程所遇到的同一级或内部嵌套级别的后续并行区域.说明:此函数只能在串行代码部分调用. |
int omp_get_num_threads(void) | 返回当前线程数目.说明:如果在串行代码中调用此函数,返回值为1. |
int omp_get_max_threads(void) | 如果在程序中此处遇到未使用 num_threads() 子句指定的活动并行区域,则返回程序的最大可用线程数量.说明:可以在串行或并行区域调用,通常这个最大数量由omp_set_num_threads()或OMP_NUM_THREADS环境变量决定. |
int omp_get_thread_num(void) | 返回当前线程id.id从1开始顺序编号,主线程id是0 |
int omp_get_num_procs(void) | 返回程序可用的处理器数. |
void omp_set_dynamic(int _Dynamic_threads) | 启用或禁用可用线程数的动态调整.(缺省情况下启用动态调整.)此调用只影响调用线程所遇到的同一级或内部嵌套级别的后续并行区域.如果 _Dynamic_threads 的值为非零值,启用动态调整;否则,禁用动态调整 |
int omp_get_dynamic(void) | 确定在程序中此处是否启用了动态线程调整.启用了动态线程调整时返回非零值;否则,返回零值 |
int omp_in_parallel(void) | 确定线程是否在并行区域的动态范围内执行.如果在活动并行区域的动态范围内调用,则返回非零值;否则,返回零值.活动并行区域是指 IF 子句求值为 TRUE 的并行区域. |
void omp_set_nested(int _Nested) | 启用或禁用嵌套并行操作.此调用只影响调用线程所遇到的同一级或内部嵌套级别的后续并行区域._Nested 的值为非零值时启用嵌套并行操作;否则,禁用嵌套并行操作.缺省情况下,禁用嵌套并行操作. |
int omp_get_nested(void) | 确定在程序中此处是否启用了嵌套并行操作.启用嵌套并行操作时返回非零值;否则,返回零值. |
void omp_init_lock(omp_lock_t * _Lock) | 初始化互斥锁 |
void omp_init_nest_lock(omp_nest_lock_t * _Lock) | 初始化一个嵌套互斥锁 |
void omp_destroy_lock(omp_lock_t * _Lock),void omp_destroy_nest_lock(omp_nest_lock_t * _Lock) | 结束一个(嵌套)互斥锁的使用并释放内存 |
void omp_set_lock(omp_lock_t * _Lock,void omp_set_nest_lock(omp_nest_lock_t * _Lock) | 获得一个(嵌套)互斥锁 |
void omp_unset_lock(omp_lock_t * _Lock),void omp_unset_nest_lock(omp_nest_lock_t * _Lock) | 释放一个(嵌套)互斥锁 |
int omp_test_lock(omp_lock_t * _Lock), int omp_test_nest_lock(omp_nest_lock_t * _Lock) | 试图获得一个(嵌套)互斥锁,并在成功时放回真(true),失败是返回假(false) |
double omp_get_wtime(void) | 获取wall clock time,返回一个double的数,表示从过去的某一时刻经历的时间,一般用于成对出现,进行时间比较. 此函数得到的时间是相对于线程的,也就是每一个线程都有自己的时间. |
double omp_get_wtick(void) | 得到clock ticks的秒数 |
环境变量:
指令 | 作用 | 样例 |
---|---|---|
OMP_SCHEDULE | 此变量的值确定如何在处理器上调度循环的迭代 | export OMP_SCHEDULE=“guided, 4” ,export OMP_SCHEDULE=“dynamic” |
OMP_NUM_THREADS | 设置执行期间要使用的最大线程数。 | export OMP_NUM_THREADS=8 |
OMP_DYNAMIC | 启用或禁用动态调整可用于执行并行区域的线程数。有效值为TRUE或FALSE。 | export OMP_DYNAMIC=TRUE |
OMP_PROC_BIND | 启用或禁用绑定到处理器的线程。有效值为TRUE或FALSE。 | export OMP_PROC_BIND=TRUE |
OMP_NESTED | 启用或禁用嵌套并行性。有效值为TRUE或FALSE。 | export OMP_NESTED=TRUE |
OMP_STACKSIZE | 控制创建(非主)线程的堆栈大小。 | export OMP_STACKSIZE=2000500B ,export OMP_STACKSIZE=“3000 k " ,export OMP_STACKSIZE=10M ,export OMP_STACKSIZE=” 10 M " ,export OMP_STACKSIZE=“20 m " ,export OMP_STACKSIZE=” 1G" ,export OMP_STACKSIZE=20000 |
OMP_WAIT_POLICY | 提供有关等待线程的所需行为的OpenMP实现的提示。兼容的OpenMP实现可能会也可能不会遵守环境变量的设置。有效值为ACTIVE和PASSIVE。ACTIVE指定等待线程应该主动处于活动状态,即在等待时消耗处理器周期。PASSIVE指定等待线程应该主要是被动的,即在等待时不消耗处理器周期。ACTIVE和PASSIVE行为的细节是实现定义的 | export OMP_WAIT_POLICY=ACTIVE ,export OMP_WAIT_POLICY=active ,export OMP_WAIT_POLICY=PASSIVE ,export OMP_WAIT_POLICY=passive |
OMP_MAX_ACTIVE_LEVELS | 控制嵌套活动并行区域的最大数量。此环境变量的值必须是非负整数。如果请求的OMP_MAX_ACTIVE_LEVELS值大于实现可以支持的嵌套活动并行级别的最大数量,或者该值不是非负整数,则程序的行为是实现定义的。 | export OMP_MAX_ACTIVE_LEVELS=2 |
OMP_THREAD_LIMIT | 设置要用于整个OpenMP程序的OpenMP线程数。此环境变量的值必须是正整数。如果请求的OMP_THREAD_LIMIT值大于实现可以支持的线程数,或者该值不是正整数,则程序的行为是实现定义的。 | export OMP_THREAD_LIMIT=8 |
for
#include <omp.h> #define N 1000 #define CHUNKSIZE 100 main(int argc, char *argv[]) { int i, chunk; float a[N], b[N], c[N]; /* Some initializations */ for (i=0; i < N; i++) a[i] = b[i] = i * 1.0; chunk = CHUNKSIZE; #pragma omp parallel shared(a,b,c,chunk) private(i) { #pragma omp for schedule(dynamic,chunk) nowait for (i=0; i < N; i++) c[i] = a[i] + b[i]; } /* end of parallel region */ }
section
#include <omp.h> #define N 1000 main(int argc, char *argv[]) { int i; float a[N], b[N], c[N], d[N]; /* Some initializations */ for (i=0; i < N; i++) { a[i] = i * 1.5; b[i] = i + 22.35; } #pragma omp parallel shared(a,b,c,d) private(i) { #pragma omp sections nowait { #pragma omp section for (i=0; i < N; i++) c[i] = a[i] + b[i]; #pragma omp section for (i=0; i < N; i++) d[i] = a[i] * b[i]; } /* end of sections */ } /* end of parallel region */ }
clause
#include <omp.h> main(int argc, char *argv[]) { int i, n, chunk; float a[100], b[100], result; /* Some initializations */ n = 100; chunk = 10; result = 0.0; for (i=0; i < n; i++) { a[i] = i * 1.0; b[i] = i * 2.0; } #pragma omp parallel for \ default(shared) private(i) \ schedule(static,chunk) \ reduction(+:result) for (i=0; i < n; i++) result = result + (a[i] * b[i]); printf("Final result= %f\n",result); }
所谓的SIMD指令,指的是single instruction multiple data,即单指令多数据运算,其目的就在于帮助CPU实现数据并行,提高运算效率。[SIMD原理],适合于多媒体应用等数据密集型运算。(https://www.cnblogs.com/ncdxlxk/p/10126617.html)
简单的例子
#pragma omp simd
for (i = 0; i < count; i++)
{
a[i] = a[i-1] + 1;
b[i] = *c + 1;
bar(i);
}
从句Clauses:
指令 | 作用 |
---|---|
simdlen(length) | 指定向量长度 |
safelen(length) | 指定向量依赖距离 |
linear(list[ : linear-step]) | 从循环归纳变量线性映射到数组订阅。 |
aligned(list[ : alignment]) | 数据对齐 |
private(list) | 私有数据 |
lastprivate(list) | 在一个循环并行执行结束后,指定变量的值为循环体在顺序最后一次执行时获取的值 |
reduction(reduction-identifier:list) | 指定自定义的缩减操作。 |
collapse(n) | 合并循环嵌套。 |
例子
#pragma omp simd simdlen(8)
for (i = 0; i < count; i++)
{
a[i] = a[i-1] + 1;
b[i] = *c + 1;
bar(i);
}
MPI是一个跨语言的通讯协议,用于编写并行计算机,支持点对点和广播。MPI的目标是高性能,大规模性,和可移植性。MPI在今天仍为高性能计算的主要模型。消息传递接口是一种编程接口标准,而不是一种具体的编程语言。简而言之,MPI标准定义了一组具有可移植性的编程接口。
int MPI_Init (int* argc ,char** argv[] )
该函数通常应该是第一个被调用的MPI函数用于并行环境初始化,其后面的代码到 MPI_Finalize()函数之前的代码在每个进程中都会被执行一次。
– 除MPI_Initialized()外, 其余所有的MPI函数应该在其后被调用。
– MPI系统将通过argc,argv得到命令行参数(也就是说main函数必须带参数,否则会出错)。
int MPI_Finalize (void)
– 退出MPI系统, 所有进程正常退出都必须调用。 表明并行代码的结束,结束除主进程外其它进程。
– 串行代码仍可在主进程(rank = 0)上运行, 但不能再有MPI函数(包括MPI_Init())。
int MPI_Comm_size (MPI_Comm comm ,int* size )
获得进程个数 size。
– 指定一个通信子,也指定了一组共享该空间的进程, 这些进程组成该通信子的group(组)。
– 获得通信子comm中规定的group包含的进程的数量。
int MPI_Comm_rank (MPI_Comm comm ,int* rank)
– 得到本进程在通信空间中的rank值,即在组中的逻辑编号(该 rank值为0到p-1间的整数,相当于进程的ID。)
int MPI_Send( void *buff, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm)
–void *buff:你要发送的变量。
–int count:你发送的消息的个数(注意:不是长度,例如你要发送一个int整数,这里就填写1,如要是发送“hello”字符串,这里就填写6(C语言中字符串未有一个结束符,需要多一位))。
–MPI_Datatype datatype:你要发送的数据类型,这里需要用MPI定义的数据类型,可在网上找到,在此不再罗列。
–int dest:目的地进程号,你要发送给哪个进程,就填写目的进程的进程号。
–int tag:消息标签,接收方需要有相同的消息标签才能接收该消息。
–MPI_Comm comm:通讯域。表示你要向哪个组发送消息。
int MPI_Recv( void *buff, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status)
–void *buff:你接收到的消息要保存到哪个变量里。
–int count:你接收消息的消息的个数(注意:不是长度,例如你要发送一个int整数,这里就填写1,如要是发送“hello”字符串,这里就填写6(C语言中字符串未有一个结束符,需要多一位))。它是接收数据长度的上界. 具体接收到的数据长度可通过调用MPI_Get_count 函数得到。
–MPI_Datatype datatype:你要接收的数据类型,这里需要用MPI定义的数据类型,可在网上找到,在此不再罗列。
–int dest:接收端进程号,你要需要哪个进程接收消息就填写接收进程的进程号。
–int tag:消息标签,需要与发送方的tag值相同的消息标签才能接收该消息。
–MPI_Comm comm:通讯域。
–MPI_Status *status:消息状态。接收函数返回时,将在这个参数指示的变量中存放实际接收消息的状态信息,包括消息的源进程标识,消息标签,包含的数据项个数等。
#include <stdio.h> #include <string.h> #include "mpi.h" void main(int argc, char* argv[]) { int numprocs, myid, source; MPI_Status status; char message[100]; MPI_Init(&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD, &myid); MPI_Comm_size(MPI_COMM_WORLD, &numprocs); if (myid != 0) { //非0号进程发送消息 strcpy(message, "Hello World!"); MPI_Send(message, strlen(message) + 1, MPI_CHAR, 0, 99, MPI_COMM_WORLD); } else { // myid == 0,即0号进程接收消息 for (source = 1; source < numprocs; source++) { MPI_Recv(message, 100, MPI_CHAR, source, 99, MPI_COMM_WORLD, &status); printf("接收到第%d号进程发送的消息:%s\n", source, message); } } MPI_Finalize(); } /* end main */
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。