OpenMP学习小结

PhoenixGS
Jul 10, 2022
Last edited: 2022-7-19
type
Post
status
Published
date
Jul 10, 2022
slug
openmp
summary
对使用OpenMP时需要用到的各种函数等进行整理
tags
CS
高性能计算
category
icon
password
Property
Jul 19, 2022 08:35 AM
OpenMP,即Open Multi-Processing的缩写。
显式指导多线程、共享内存并行的API
并行区结构体
#pragma omp parallel [子句]
子句:
private(列表) firstprivate(列表) shared(列表) copyin(列表) reduction([归约修饰符,] 归约操作符: 列表) proc_bind(master | close | spread) allocate([分配器: 列表) if ([parallel: ] 标量表达式) num_thread(整数表达式) default(shared | none)
- 并行区结束时,所有线程会同步(有一个隐式barrier)
线程数量
调整并行区内线程数量的方式
添加
num_threads
子句调用
omp_set_num_threads()
函数设置
OMP_NUM_THREADS
环境变量添加
if
子句优先级:
pragma
里面指定 > 函数API设置 > 环境变量设置嵌套并行区
- 除非启用嵌套并行区,内层并行区只用一个线程执行
启用/禁用嵌套并行区
omp_set_nested(bool)
设置
OMP_NESTED
环境变量检查嵌套并行区是否开启
omp_get_nested()
任务分配结构体
- 任务分配结构体不创建新进程
- 进入任务分配结构体时,各线程不同步
- 离开任务分配结构体时,各线程同步
三种典型的任务分配结构体
任务分配结构体需要包含在并行区内部
- DO/for
- sections
- single
任务分配方式:for
#pragma omp for [子句] for (...)
子句:
private(列表) firstprivate(列表) lastprivate([lastprivate修饰符: ]列表) linear(列表[: 步长]) schedule([调度修饰符 [,调度修饰符]:] 调度策略 [,块大小]) collapse(n) ordered[(n)] allocate([分配器: 列表) order(concurrent) reduction([归约修饰符,] 归约操作符: 列表) nowait
以下两种写法等价:
#pragma omp parallel { #pragma omp for for (i = 0; i < 25; i++) { printf("Foo"); } } #pragma omp parallel for for (i = 0; i < 25; i++) { printf("Foo"); }
调度方式
static
将各循环迭代组成块,块数与线程数相等,各块包含的迭代次数尽量平均 每个线程执行一块
dynamic
将各循环迭代分割为块,每块默认只含一次迭代 每当某线程执行完一块,会动态地再请求一块
guided
与dynamic
类似,但块的大小会从大到小动态变化,以改善负载均衡
runtime
运行时再通过环境变量OMP_SCHEDULE
设定
auto
由编译器或系统自动决定
collapse
子句:将嵌套循环的迭代统一调度,而不是串行执行内层循环
任务分配方式:sections
- 将不同片段的代码组成不同section,再分配给不同线程
- 各section命令需要包含在一个sections命令中
- 每个section只被一个线程执行一次
- 线程和section的对应关系是不确定的
#pragma omp sections [子句] { #pragma omp section 语句块 #pragma omp section 语句块 ... }
子句:
private(列表) firstprivate(列表) lastprivate([lastprivate修饰符: ]列表) allocate([分配器: 列表) reduction([归约修饰符,] 归约操作符: 列表) nowait
#pragma omp parallels num_threads(2) { #pragma omp sections { #pragma omp section { for (int i = 0; i < n; i++) { c[i] = a[i] + b[i]; } } #pragma omp section { for (int i = 0; i < n; i++) { d[i] = a[i] + b[i]; } } } }
任务分配方式:single
- 可用于执行非线程安全的操作(例如I/O)
- 不执行此代码段的线程将等待此代码段执行结束(除非用了
nowait
子句)
数据共享
- 默认线程间共享变量 全局变量、static变量 除了并行循环的循环下标外,所有并行区内、任务分配结构体外局部变量
- 默认线程内私有变量 并行循环的循环下标 任务分配结构体内的局部变量 并行区内调用的函数中的局部变量
- 作用域可以通过子句显式控制
private, shared, firstprivate, lastprivate
default, reduction, copyin
private(var_list)
将列表中的变量声明为线程私有 变量在进入并行区时不会初始化,离开并行区后不会保留
shared(var_list)
将列表中的变量声明为线程间共享
firstprivate(var_list)
与private
类似,但变量在进入并行区时会被初始化成进入并行区前的值
lastprivate(var_list)
与private
类似,但变量在离开并行区后,其在最后一次迭代时的值会被保留
线程同步
#pragma omp critical
只有一个线程执行临界区
#pragma omp barrier
等待所有线程到达此处后继续运行
#pragma omp single
只用一个线程执行代码
#pragma omp master
只用主线程执行一段代码(执行完毕后没有隐式barrier)
#pragma omp atomic
使用原子指令访问共享内存
- OpenMP的锁函数:
omp_set_lock(l); /* 临界区代码 */ omp_unset_lock(l);
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) //释放锁 int omp_test_lock(omp_lock_t *lock) //测试某个锁是否已被获取。此函数不会阻塞
- 当需要获取一个共享变量的最新值时,必须保证变量被flush
#pragma omp flush [内存序] [(变量列表)] //手动flush acq_rel, release, acquire //内存序 parallel(进出)/critical(进出)/ordered(进出)/for(出)/sections(出)/single(出) //自动flush
nowait
子句:取消barrier#pragma omp for nowait for (int i = 0; i < n; i++) { a[i] = bigFunc1(i); } #pragma omp for nowait for (int j = 0; j < m; j++) { b[j] = bigFunc2(j); }
- OpenMP同步方法
- Barrier
- 临界区(底层采用锁实现)
- 显式的锁操作
- 原子操作Atomic
- 单线程任务分配结构体
归约
使用
reduction
子句reduction(op: list) //list中的变量需为共享变量
OpenMP示例代码
#include <stdio.h> #include <stdlib.h> #include <omp.h> void Hello(void); /** 线程函数 */ int main(int argc, char* argv[]) { /** 从命令行得到线程数 */ int thread_count = strtol(argv[1], NULL, 10); #pragma omp parallel num_threads(thread_count) //并行指导语句 Hello(); return 0; } /** main */ void Hello(void) { int my_rank = omp_get_thread_num(); //函数调用-线程ID int thread_count = omp_get_num_threads(); //线程数 printf("Hello from thread %d of %d\n", my_rank, thread_count); } /** Hello */
编译运行
gcc −g −Wall −fopenmp −o main main.c
因编译器不同而不同
OMP_NUM_THREADS=4 ./main
环境变量设置
常用的库函数
void omp_set_num_threads(int num_threads) int omp_get_num_threads() int omp_get_thread_num() int omp_get_thread_limit() int omp_get_num_procs() int omp_in_parallel()
常用的环境变量
OMP_NUM_THREADS OMP_PROC_BIND OMP_WAIT_POLICY

参考资料
翟季冬老师高性能计算及应用课件
- Catalog
- About
0%