OpenMP共享内存多核并行计算
OpenMP共享内存多核并行计算
一、参考资料
openMP_demo
OpenMP入门
OpenMP 教程(一) 深入剖析 OpenMP reduction 子句
OpenMP初始CMakeLists配置
openmp,mpi,cuda,avx2/avx512指令集的使⽤
二、OpenMP相关介绍
重要提示:OpenMP提供了对并行算法的高层抽象描述,特别适合在多核CPU机器上的并行程序设计。编译器根据程序中添加的pragma指令,自动将程序并行处理,使用OpenMP降低了并行编程的难度和复杂度。当编译器不支持OpenMP时,程序会退化成普通(串行)程序。
1. OpenMP简介
OpenMP(Open Multi-Processing)是一种用于共享内存并行系统的多线程程序涉及方案,支持C/C++。OpenMP提供了对并行算法的高层抽象描述,能够在多个处理器核心中实现并行计算,提高程序的执行效率。编译器根据程序中添加的pragma指令,自动将程序并行处理,使用OpenMP降低了并行编程的难度和复杂度。当编译器不支持OpenMP时,程序会退化成普通(串行)程序,程序中已有的OpenMP指令不会影响程序的正常编译运行。
很多主流的编译环境都内置了OpenMP,在VisualStudio中,启动OpenMP很简单。在项目上右键->属性->配置属性->C/C+±>语言->OpenMP支持,选择“是”即可。
2. 共享内存模型
OpenMP是为多处理器和多核共享内存机器设计的。处理单元(CPU核心)的数量,决定了OpenMP的并行性。
3. 混合并行模型
OpenMP适用于单节点并行,MPI与OpenMP相结合实现分布式内存并行,这通常被称为混合并行模型。
- OpenMP用于每个节点(一台计算机)上的计算密集型工作;
- MPI用于实现节点之间的通信和数据共享。
4. Fork-Join 模型
OpenMP使用并行执行的 Fork-Join 模型。
- Fork:主线程创建一组并行线程;
- Join:团队线程在并行区域分别进行计算,它们将进行同步与终止,只留下主线程。
上图中, parallel region 是并行域,在并行域内多线程并发,在并行域之间由主线程(master)线性执行。
5. barrier 同步机制
barrier 用于并行域内代码的线程同步,线程执行到 barrier 时要停下等待,直到所有线程都执行到 barrier 时,才继续往下执行,从而实现线程的同步。
#include <stdio.h>
int main(void)
{
int th_id, nthreads;
#pragma omp parallel private(th_id)
{
th_id = __builtin_omp_get_thread_num();
printf("Hello World from thread %d\n", th_id);
#pragma omp barrier
if (th_id == 0) {
nthreads = __builtin_omp_get_num_threads();
printf("There are %d threads\n", nthreads);
}
}
return 0;
}
yoyo@yoyo:~/PATH/TO$ gcc -fopenmp demo.c -o demo
yoyo@yoyo:~/PATH/TO$ ./demo
Hello World from thread 10
Hello World from thread 3
Hello World from thread 2
Hello World from thread 6
Hello World from thread 4
Hello World from thread 7
Hello World from thread 0
Hello World from thread 5
Hello World from thread 11
Hello World from thread 8
Hello World from thread 1
Hello World from thread 9
There are 12 threads
三、常用操作
1. 常用指令
# 安装OpenMP
sudo apt-get install libomp-dev
# 使用gcc编译OpenMP程序
gcc -fopenmp demo.c -o demo
# 使用g++编译OpenMP程序
g++ -fopenmp demo.cpp -o demo
2. 重要操作
(1)并行区域:使用 #pragma omp parallel 指令来定义并行区域。
(2)线程编号:使用 omp_get_thread_num() 函数获取当前线程的编号。
(3)线程总数:使用 omp_get_num_threads() 函数获取总的线程数。
(4)数据共享:可以使用 private 和 shared 等关键字来声明变量的共享状态。
(5)同步机制:可以使用 #pragma omp barrier 指令来实现线程的同步。
3. 查看是否支持OpenMP
#include <stdio.h>
int main()
{
#if _OPENMP
printf("support openmp\n");
#else
printf("not support openmp\n");
#endif
return 0;
}
yoyo@yoyo:~/PATH/TO$ gcc -fopenmp demo.c -o demo
yoyo@yoyo:~/PATH/TO$ ./demo-1
support openmp
4. Hello World
#include <stdio.h>
int main(void)
{
#pragma omp parallel
{
printf("Hello, world. \n");
}
return 0;
}
yoyo@yoyo:~/PATH/TO$ gcc -fopenmp demo.c -o demo
yoyo@yoyo:~/PATH/TO$ ./demo
Hello, world.
Hello, world.
Hello, world.
Hello, world.
Hello, world.
Hello, world.
Hello, world.
Hello, world.
Hello, world.
Hello, world.
Hello, world.
Hello, world.
由于没有指定线程数,默认数量为CPU核心数。
#include <stdio.h>
int main(void)
{
// 指定线程数量
#pragma omp parallel num_threads(6)
{
printf("Hello, world. \n");
}
return 0;
}
5. #pragma omp parallel for
omp_get_thread_num :获取当前线程id;
#include <stdio.h>
#include <omp.h>
#include <stdlib.h>
int main(void) {
#pragma omp parallel for
for (int i=0; i<12; i++) {
printf("OpenMP Test, th_id: %d\n", omp_get_thread_num());
}
return 0;
}
yoyo@yoyo:~/PATH/TO$ gcc -fopenmp demo.c -o demo
yoyo@yoyo:~/PATH/TO$ ./demo
OpenMP Test, th_id: 8
OpenMP Test, th_id: 3
OpenMP Test, th_id: 1
OpenMP Test, th_id: 9
OpenMP Test, th_id: 5
OpenMP Test, th_id: 0
OpenMP Test, th_id: 6
OpenMP Test, th_id: 11
OpenMP Test, th_id: 2
OpenMP Test, th_id: 7
OpenMP Test, th_id: 4
OpenMP Test, th_id: 10
6. reduction 规约操作
6.1 引言
#include <stdio.h>
#include <omp.h>
#include <stdlib.h>
int main(void) {
int sum = 0;
#pragma omp parallel for
for (int i=1; i<=100; i++) {
sum += i;
}
printf("%d", sum);
return 0;
}
yoyo@yoyo:~/PATH/TO$ gcc -fopenmp demo.c -o demo
yoyo@yoyo:~/PATH/TO$ ./demo
1173yoyo@yoyo:~/PATH/TO$ ./demo
2521yoyo@yoyo:~/PATH/TO$ ./demo
3529yoyo@yoyo:~/PATH/TO$ ./demo
2174yoyo@yoyo:~/PATH/TO$ ./demo
1332yoyo@yoyo:~/PATH/TO$ ./demo
1673yoyo@yoyo:~/PATH/TO$ ./demo
1183yoyo@yoyo:~/PATH/TO$
执行多次,每次结果都不一样,原因是线程对同一个资源产生了竞争。对于 sum += i 这一行,可以改写为 sum = sum + i ,多线程并行对 sum 同时写,产生冲突。为了解决这个问题,可以使用 reduction 。
6.2 reduction 简介
reduction(操作符:变量)
先以 sum 求和函数为例:
#include <stdio.h>
#include <omp.h>
#include <stdlib.h>
int main(void) {
int sum = 0;
#pragma omp parallel for reduction(+:sum)
for (int i=1; i<=100; i++) {
sum += i;
}
printf("%d", sum);
return 0;
}
yoyo@yoyo:~/PATH/TO$ gcc -fopenmp demo.c -o demo
yoyo@yoyo:~/PATH/TO$ ./demo
5050yoyo@yoyo:~/PATH/TO$ ./demo
5050yoyo@yoyo:~/PATH/TO$ ./demo
5050yoyo@yoyo:~/PATH/TO$ ./demo
5050yoyo@yoyo:~/PATH/TO$ ./demo
5050yoyo@yoyo:~/PATH/TO$ ./demo
5050yoyo@yoyo:~/PATH/TO$
上述代码中, reduction(+:sum) 表示在每个线程对变量sum进行拷贝,然后在线程中使用这个拷贝的变量,这样就不存在数据竞争的问题,因为对于每个线程,使用的 sum 数据都不一样。在 reduction 中还有一个加号 + ,这个加号表示如何进行规约操作。所谓规约操作,简单来说就是多个数据逐步进行操作,最终得到一个不能够在进行规约的数据。
例如,在上面的程序中,规约操作为 + ,因此需要将线程1和线程2的数据进行 + 操作,即线程1的sum值加上线程2的sum值,然后将得到的结果赋值给全局变量sum,其他线程以此类推,最终得到的全局变量sum即为正确结果。
如果有4个线程,那么就有4个线程本地的 sum,每个线程拷贝一份 sum。那么规约(reduction)操作的结果等于:
( ( ( s u m 1 + s u m 2 ) + s u m 3 ) + s u m 4 ) (((sum_1 + sum2) + sum_3) + sum_4)(((sum1 +sum2)+sum3 )+sum4 )
其中, sum_i 表示第i个线程得到的sum。
7. 测试样例
openmp_example.cpp
#include <stdio.h>
#include <omp.h>
int main() {
int num_threads = 4;
#pragma omp parallel num_threads(num_threads)
{
int thread_id = omp_get_thread_num();
printf("Hello from thread %d\n", thread_id);
}
return 0;
}
交叉编译openMP程序:
arm-linux-gnueabihf-g++ -fopenmp -o openmp_example openmp_example.cpp
将编译好的可执行文件传输到目标平台上运行:
/mnt # ./openmp_example
Hello from thread 2
Hello from thread 0
Hello from thread 1
Hello from thread 3
可能出现以下问题:
error while loading shared libraries: libgomp.so.1: cannot open shared object file: No such file or directory
解决方法:将gcc交叉编译工具链中的 libgopenmp* 依赖库拷贝到开发板中,并设置环境变量。
# 拷贝
scp /PATH/TO/arm-linux-gnueabihf/arm-linux-gnueabihf/lib/libgomp* 192.168.137.30:/path/to/your/libs
# 设置环境变量
export LD_LIBRARY_PATH=/path/to/your/libs:$LD_LIBRARY_PATH
8. CMakeLists.txt 文件中使用
# 查找OpenMP库
find_package(OpenMP REQUIRED)
if(OpenMP_FOUND)
message(STATUS "found openmp")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}"
else()
message(FATAL_ERROR "openmp not found!")
endif()
# 设置编译选项
targetcompileoptions(dladetect PRIVATE ${OpenMPCXX_FLAGS})
# 链接OpenMP库
target_link_libraries(dla_detect PRIVATE ${OpenMP_LIBRARIES})