问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

OpenMP共享内存多核并行计算

创作时间:
作者:
@小白创作中心

OpenMP共享内存多核并行计算

引用
CSDN
1.
https://blog.csdn.net/m0_37605642/article/details/132675523

一、参考资料

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})
© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号