OpenMP 使用介绍
-
OpenMP 基本概念
Open Multi-Processing的缩写,是一个应用程序接口(API),可用于显式指导多线程、共享内存的并行性。
在项目程序已经完成好的情况下不需要大幅度的修改源代码,只需要加上专用的pragma来指明自己的意图,由此编译器可以自动将程序进行并行化,并在必要之处加入同步互斥以及通信。当选择忽略这些pragma,或者编译器不支持OpenMp时,程序又可退化为通常的程序(一般为串行),代码仍然可以正常运作,只是不能利用多线程来加速程序执行。OpenMP提供的这种对于并行描述的高层抽象降低了并行编程的难度和复杂度,这样程序员可以把更多的精力投入到并行算法本身,而非其具体实现细节。对基于数据分集的多线程程序设计,OpenMP是一个很好的选择。
OpenMP支持的语言包括C/C++、Fortran;而支持OpenMP的编译器VS、gcc、clang等都行。可移植性也很好:Unix/Linux和Windows
-
OpenMP编程模型
内存共享模型:OpenMP是专为多处理器/核,共享内存机器所设计的。底层架构可以是UMA和NUMA。即(Uniform Memory Access和Non-Uniform Memory Access)
2.1基于线程的并行性
• OpenMP仅通过线程来完成并行
• 一个线程的运行是可由操作系统调用的最小处理单
• 线程们存在于单个进程的资源中,没有了这个进程,线程也不存在了
• 通常,线程数与机器的处理器/核数相匹配,然而,实际使用取决与应用程序
2.2明确的并行
• OpenMP是一种显式(非自动)编程模型,为程序员提供对并行化的完全控制
• 一方面,并行化可像执行串行程序和插入编译指令那样简单
• 另一方面,像插入子程序来设置多级并行、锁、甚至嵌套锁一样复杂
2.3 Fork-Join模型
• OpenMP就是采用Fork-Join模型
• 所有的OpenML程序都以一个单个进程——master thread开始,master threads按顺序执行知道遇到第一个并行区域
• Fork:主线程创造一个并行线程组
• Join:当线程组完成并行区域的语句时,它们同步、终止,仅留下主线程
2.4 数据范围
• 由于OpenMP时是共享内存模型,默认情况下,在共享区域的大部分数据是被共享的
• 并行区域中的所有线程可以同时访问这个共享的数据
• 如果不需要默认的共享作用域,OpenMP为程序员提供一种“显示”指定数据作用域的方法
2.5嵌套并行
• API提供在其它并行区域放置并行区域
• 实际实现也可能不支持
2.6动态线程
• API为运行环境提供动态的改变用于执行并行区域的线程数
• 实际实现也可能不支持
3.openmp使用
需要使用openmp就需要引入omp.h库文件。然后在编译时添加参数 -fopenmp即可。 在具体需要进行并行运算的部分,使用 #pragma omp 指令[子句] 来告诉编译器如何并行执行对应的语句。 常用的指令如下:
parallel:用在一个结构块之前,表示这段代码将被多个线程并行执行;
for:用于for循环语句之前,表示将循环计算任务分配到多个线程中并行执行,以实现任务分担,必须由编程人员自己保证每次循环之间无数据相关性;parallel for:parallel和for指令的结合,也是用在for循环语句之前,表示for循环体的代码将被多个线程并行执行,它同时具有并行域的产生和任务分担两个功能;
sections:用在可被并行执行的代码段之前,用于实现多个结构块语句的任务分担,可并行执行的代码段各自用section指令标出(注意区分sections和section);
parallel sections:parallel和sections两个语句的结合,类似于parallel for;
single:用在并行域内,表示一段只被单个线程执行的代码;
critical:用在一段代码临界区之前,保证每次只有一个OpenMP线程进入;
flush:保证各个OpenMP线程的数据影像的一致性;
barrier:用于并行域内代码的线程同步,线程执行到barrier时要停下等待,直到所有线程都执行到barrier时才继续往下执行;
atomic:用于指定一个数据操作需要原子性地完成;
master:用于指定一段代码由主线程执行;
threadprivate:用于指定一个或多个变量是线程专用,后面会解释线程专有和私有的区别。
常用的子句如下:
private:指定一个或多个变量在每个线程中都有它自己的私有副本;
firstprivate:指定一个或多个变量在每个线程都有它自己的私有副本,并且私有变量要在进入并行域或任务分担域时,继承主线程中的同名变量的值作为初值;
lastprivate:是用来指定将线程中的一个或多个私有变量的值在并行处理结束后复制到主线程中的同名变量中,负责拷贝的线程是for或sections任务分担中的最后一个线程;
reduction:用来指定一个或多个变量是私有的,并且在并行处理结束后这些变量要执行指定的归约运算,并将结果返回给主线程同名变量;
nowait:指出并发线程可以忽略其他制导指令暗含的路障同步;
num_threads:指定并行域内的线程的数目;
schedule:指定for任务分担中的任务分配调度类型;
shared:指定一个或多个变量为多个线程间的共享变量;
ordered:用来指定for任务分担域内指定代码段需要按照串行循环次序执行;
copyprivate:配合single指令,将指定线程的专有变量广播到并行域内其他线程的同名变量中;
copyin:用来指定一个threadprivate类型的变量需要用主线程同名变量进行初始化;
default:用来指定并行域内的变量的使用方式,缺省是shared
另外,openmp还提供了一些列的api函数来获取并行线程的状态或控制并行线程的行为,常用的OpenMP API函数以及说明:
omp_in_parallel - 判断当前是否在并行域中。
omp_get_thread_num - 获取线程号
omp_set_num_threads - 设置并行域中线程格式
omp_get_num_threads - 返回并行域中线程数
omp_get_dynamic - 判断是否支持动态改变线程数目
omp_get_max_threads - 获取并行域中可用的最大的并行线程数目
omp_get_num_procs - 返回系统中处理器的个数
环境变量
OpenMP中定义一些环境变量,可以通过这些环境变量控制OpenMP程序的行为,常用的环境变量:
OMP_SCHEDULE:用于for循环并行化后的调度,它的值就是循环调度的类型;
OMP_NUM_THREADS:用于设置并行域中的线程数;
OMP_DYNAMIC:通过设定变量值,来确定是否允许动态设定并行域内的线程数;
OMP_NESTED:指出是否可以并行嵌套。
4.openmp 实例1
#include <iostream>
#include <cstdlib>
#include <stdio.h>
#include <time.h>
using namespace std;
const int NumChunks = 8;
const int ChunkSize = 1024*16;
const int NumElements = NumChunks*ChunkSize;
#define RAND_MAX_A 64
#define RAND_MAX_B 256
#define EPISILON 0.00001
float srcA [NumElements];
float srcB [NumElements];
float dst [NumElements];
float Golden[NumElements];
void vadd_openmp(float *a, float *b, float *c, int size)
{
#pragma omp target map(to:a[0:size],b[0:size]) map(from:c[0:size])
{
int i;
#pragma omp parallel for
for (i = 0; i < size; i++)
c[i] = a[i] + b[i];
}
}
#pragma omp declare target
void compute(float *a, float *b, float *c, int si, int size);
#pragma omp end declare target
void vadd_openmp_t(float *a, float *b, float *c, int NumChunks, int ChunkSize)
{
int NumElements = NumChunks*ChunkSize;
#pragma omp target map(to:a[0:NumElements],b[0:NumElements],NumChunks,ChunkSize) \
map(from:c[0:NumElements])
{
int i;
#pragma omp parallel
{
#pragma omp master
for (i = 0; i < NumChunks; i++)
{
int start_index = i * ChunkSize;
#pragma omp task firstprivate (start_index, ChunkSize)
compute(a, b, c, start_index, ChunkSize);
}
}
}
}
void compute(float *a, float *b, float *c, int si, int size)
{
int i;
for (i = si; i < si+size; i++)
c[i] = a[i] + b[i];
}
int main()
{
int num_errors = 0;
const int print_nerrors = 12;
srand(time(NULL));
for (int i=0; i < NumElements; ++i)
{
srcA[i] = (rand() % RAND_MAX_A + 1) * 1.0;
srcB[i] = (rand() % RAND_MAX_B + 1) * 1.0;
dst[i] = 0;
Golden[i] = srcA[i] + srcB[i];
}
vadd_openmp(srcA,srcB,dst,NumElements);
for (int i=0; i < NumElements; ++i)
{
if (Golden[i] - dst[i] < -EPISILON || Golden[i] - dst[i] > EPISILON)
{
if((num_errors += 1) < print_nerrors)
printf("Error %d: %f <==> %f\n", i, Golden[i], dst[i]);
}
}
if (num_errors > 0) cout << "FAIL with " << num_errors << " errors!\n";
else cout << "PASS!" << endl;
vadd_openmp_t(srcA,srcB,dst,NumChunks, ChunkSize);
for (int i=0; i < NumElements; ++i)
{
if (Golden[i] - dst[i] < -EPISILON || Golden[i] - dst[i] > EPISILON)
{
if((num_errors += 1) < print_nerrors)
printf("Error %d: %f <==> %f\n", i, Golden[i], dst[i]);
}
}
if (num_errors > 0) cout << "FAIL with " << num_errors << " errors!\n";
else cout << "PASS!" << endl;
return 0;
}
5.openmp 实例2
#include <iostream>
#include <cstdlib>
#include <stdio.h>
#include <time.h>
using namespace std;
const int NumElements = 8*1024;
#define RAND_MAX_A 64
#define RAND_MAX_B 256
#define EPISILON 0.00001
float srcA [NumElements];
float srcB [NumElements];
float dst [NumElements];
float Golden[NumElements];
void vadd_sub_section(float *a, float *b, float *c, int size)
{
int start = 0;
#pragma omp target data map(to:a[start:size], b[start:size]) map(tofrom:c[start:size])
{
int i;
int chunk_size = size/2;
#pragma omp target map(to:a[start:chunk_size], b[start:chunk_size]) \
map(tofrom:c[start:chunk_size])
{
#pragma omp parallel for private(i)
for (i = start; i < start+chunk_size; i++)
c[i] = a[i] + b[i];
}
start = chunk_size;
#pragma omp target map(to:a[start:chunk_size], b[start:chunk_size]) \
map(tofrom:c[start:chunk_size])
{
#pragma omp parallel for private(i)
for (i = start; i < start+chunk_size; i++)
c[i] = a[i] + b[i];
}
}
}
int main()
{
int num_errors = 0;
const int print_nerrors = 12;
srand(time(NULL));
for (int i=0; i < NumElements; ++i)
{
srcA[i] = (rand() % RAND_MAX_A + 1) * 1.0;
srcB[i] = (rand() % RAND_MAX_B + 1) * 1.0;
dst[i] = 0;
Golden[i] = srcA[i] + srcB[i];
}
vadd_sub_section(srcA,srcB,dst,NumElements);
for (int i=0; i < NumElements; ++i)
{
if (Golden[i] - dst[i] < -EPISILON || Golden[i] - dst[i] > EPISILON)
{
if((num_errors += 1) < print_nerrors)
printf("Error %d: %f <==> %f\n", i, Golden[i], dst[i]);
}
}
if (num_errors > 0) cout << "FAIL with " << num_errors << " errors!\n";
else cout << "PASS!" << endl;
return 0;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)