OpenMP学习笔记

OpenMP是一个用于编写并行程序的应用编程接口。

1.构成OpenMP通用核心的编译指令、运行时库函数和子句以及相关的多线程计算基本概念:

2.用于OpenMP通用核心的解决方案栈:

3.openmp构造线程的基本形式

OpenMP 是一种基于共享内存(Shared Memory)的并行编程标准。 对于 C++ 开发者来说,它最直观的特点是非侵入式的------我们不需要像 std::thread 那样重写很多代码,只需要在循环或代码块前加上 #pragma 编译指令,编译器就会自动帮我们将任务分配到多个 CPU 核心上执行。

程序开始时是单线程(Master Thread)执行;遇到并行指令(如 #pragma omp parallel)时,主线程会 Fork 出一组子线程(Team of Threads)来并行处理任务;任务结束后,这些线程会同步并 Join(汇合),释放资源,恢复到单线程继续执行。 这非常适合处理那种'数据相互独立、计算量大'的循环任务(Data Parallelism)。

返回线程序号:

返回当前线程组中的线程总数:

示例:

cpp 复制代码
#include <stdio.h>
#include <omp.h>

int main(){
    omp_set_num_threads(4);
    int size_of_team;
    #pragma omp parallel

    {
        int ID = omp_get_thread_num();
        int NThrds = omp_get_num_threads();
        if(ID == 0) size_of_team = NThrds;
    }
    printf("We just did the join on a team of size %d", size_of_team);
}

命令为:

bash 复制代码
 gcc -fopenmp demo1.c
bash 复制代码
.\a.exe

输出:

bash 复制代码
 We just did the join on a team of size 4

并行化练习:

将这个程序并行化:

cpp 复制代码
#include <stdio.h>
#include <omp.h>

static long num_steps = 100000000;
double step;

int main ()
{
    int i;
    double x, pi, sum = 0.0;
    double start_time, run_time;

    step = 1.0 / (double) num_steps;

    start_time = omp_get_wtime();

    for (i = 0; i < num_steps; i++){
        x = (i + 0.5) * step;
        sum += 4.0 / (1.0 + x * x);
    }

    pi = step * sum;
    run_time = omp_get_wtime() - start_time;
    
    printf(" pi = %lf , %ld steps, %lf secs\n ", pi, num_steps, run_time);
}

并行化前,我电脑上运行时间: 0.185000 secs

方法1:对循环迭代按周期划分

cpp 复制代码
#include <stdio.h>
#include <omp.h>

#define NTHREADS 4

static long num_steps = 100000000;
double step;

int main ()
{
    int i;
    int j, actual_nthreads;
    double x, pi;
    double start_time, run_time;
    double sum[NTHREADS] = {0.0};

    step = 1.0 / (double) num_steps;

    omp_set_num_threads(NTHREADS);

    start_time = omp_get_wtime();

    #pragma omp parallel
    {
        int i;
        int id = omp_get_thread_num();
        int numthreads = omp_get_num_threads();
        double x;
        double local_sum = 0.0;

        if(id == 0) actual_nthreads = numthreads;

        for (i = id; i < num_steps; i += numthreads){
            x = (i + 0.5) * step;
            sum[id] += 4.0 / (1.0 + x * x); //
        }
    }

    pi = 0;
    for(i = 0; i < actual_nthreads; i++)
        pi += sum[i];

    pi = step * pi;
    run_time = omp_get_wtime() - start_time;
    
    // 
    printf(" pi = %lf , %ld steps, %lf secs\n ", pi, num_steps, run_time);
}

但是实际测试发现,运行时间为: 0.453000 secs

时间变长了,原因是定义的数组 sum[NTHREADS] 太小了,导致不同线程在争抢同一块 CPU 缓存行,也就是伪共享问题。

现代 CPU 的缓存行通常是 64 字节 。整个数组大小 = 4 × 8 = 32 字节。sum[0], sum[1], sum[2], sum[3] 极大概率全部挤在同一个 64 字节的缓存行里。

  • 核心 0 想更新 sum[0]。为了写入,它必须独占这行缓存,这会导致核心 1、2、3 缓存里的这行数据失效。
  • 核心 1 想更新 sum[1]。它发现缓存失效了,必须重新从内存(或 L3)拉取数据,并告诉其他核心"你们的缓存失效了"。
  • 核心 2 想更新 sum[2]......
  • 核心 3 想更新 sum[3]......

这就好比 4 个人在一张小纸条上同时写字,每个人写之前都要把纸条从别人手里抢过来。在这个 1 亿次的循环中,CPU 核心把大量时间花在了争抢缓存行的所有权(Cache Coherency 协议,如 MESI)上,而不是在做计算。这比单线程没有任何争抢要慢得多。

这时候要避免上面的问题,有两种方法:

(1)字节填充

给数组增加第二个维度,那sum[1][0]就可以足够占满一个缓存行。

但是这样也会增加空间消耗,不过在这里还好,sum数组很小。

时间和空间的消耗要学会自己取舍。

cpp 复制代码
#include <stdio.h>
#include <omp.h>

#define NTHREADS 4
#define CBLK     8

static long num_steps = 100000000;
double step;

int main()
{
    int i, j, actual_nthreads;
    double pi, start_time, run_time;
    double sum[NTHREADS][CBLK] = {0.0};

    step = 1.0 / (double) num_steps;

    omp_set_num_threads(NTHREADS);

    start_time = omp_get_wtime();
#pragma omp parallel
    {
        int i;
        int id = omp_get_thread_num();
        int numthreads = omp_get_num_threads();
        double x;

        if (id == 0)
            actual_nthreads = numthreads;

        for (i = id; i < num_steps; i += numthreads) {
            x = (i + 0.5) * step;
            sum[id][0] += 4.0 / (1.0 + x * x);
        }
    } // end of parallel region

    pi = 0.0;
    for (i = 0; i < actual_nthreads; i++)
        pi += sum[i][0];

    pi = step * pi;

    run_time = omp_get_wtime() - start_time;
    printf("\n pi is %f in %f seconds %d thrds \n",
           pi, run_time, actual_nthreads);
}

(2)减少对数组的频繁访问

引入一个局部变量,把对数组的操作移动到for循环之外:

cpp 复制代码
    #pragma omp parallel
    {
        int i;
        int id = omp_get_thread_num();
        int numthreads = omp_get_num_threads();
        double x;
        double local_sum = 0.0;

        if(id == 0) actual_nthreads = numthreads;

        for (i = id; i < num_steps; i += numthreads){
            x = (i + 0.5) * step;
            local_sum += 4.0 / (1.0 + x * x); // 原:sum[id] +=
        }
        sum[id] = local_sum; // 新增这一行
    }

完整代码:

cpp 复制代码
#include <stdio.h>
#include <omp.h>

#define NTHREADS 4

static long num_steps = 100000000;
double step;

int main ()
{
    int i;
    int j, actual_nthreads;
    double x, pi;
    double start_time, run_time;
    double sum[NTHREADS] = {0.0};

    step = 1.0 / (double) num_steps;

    omp_set_num_threads(NTHREADS);

    start_time = omp_get_wtime();

    #pragma omp parallel
    {
        int i;
        int id = omp_get_thread_num();
        int numthreads = omp_get_num_threads();
        double x;
        double local_sum = 0.0;

        if(id == 0) actual_nthreads = numthreads;

        for (i = id; i < num_steps; i += numthreads){
            x = (i + 0.5) * step;
            local_sum += 4.0 / (1.0 + x * x); // 原:sum[id] +=
        }
        sum[id] = local_sum; // 新增这一行
    }

    pi = 0;
    for(i = 0; i < actual_nthreads; i++)
        pi += sum[i];

    pi = step * pi;
    run_time = omp_get_wtime() - start_time;
    
    // 
    printf(" pi = %lf , %ld steps, %lf secs\n ", pi, num_steps, run_time);
}

运行时间为:0.048000 secs

方法2:基于循环的块状分解,在线程间分配循环工作

分块 方式中,我们需要让每个线程处理连续的一段任务。其实就是按照顺序分块,一个块给一个线程执行,例如,线程 0 处理前半部分,线程 1 处理后半部分。

cpp 复制代码
/**
 * @file demo_pi_mp_2.c
 * @author your name (you@domain.com)
 * @brief  基于循环的块状分解,在线程间分配循环工作
 *         其实就是按照顺序分块,一个块给一个线程执行
 *         
 * @version 0.1
 * @date 2025-12-22
 * 
 * @copyright Copyright (c) 2025
 * 
 */

#include <stdio.h>
#include <omp.h>

#define NTHREADS 4

static long num_steps = 100000000;
double step;

int main ()
{
    int i;
    int j, actual_nthreads;
    double x, pi;
    double start_time, run_time;
    double sum[NTHREADS] = {0.0};

    step = 1.0 / (double) num_steps;

    omp_set_num_threads(NTHREADS);

    start_time = omp_get_wtime();

    #pragma omp parallel
    {
        int i;
        int id = omp_get_thread_num();
        int numthreads = omp_get_num_threads();
        double x;
        int local_sum = 0;

        if(id == 0) actual_nthreads = numthreads;

        int block_size = num_steps / numthreads;
        int istart = id * block_size;
        int iend = (id + 1) * block_size;

        for (i = istart; i < iend; i++){
            x = (i + 0.5) * step;
            local_sum += 4.0 / (1.0 + x * x); // 原:sum[id] +=
        }
        sum[id] = local_sum; // 新增这一行
    }

    pi = 0;
    for(i = 0; i < actual_nthreads; i++)
        pi += sum[i];

    pi = step * pi;
    run_time = omp_get_wtime() - start_time;
    
    // 
    printf(" pi = %lf , %ld steps, %lf secs\n ", pi, num_steps, run_time);
}

运行时间:0.110000 secs

相关推荐
ADI_OP12 分钟前
ADAU1452的开发教程10:逻辑算法模块
算法·adi dsp中文资料·adi dsp·adi音频dsp·adi dsp开发教程·sigmadsp的开发详解
xingzhemengyou125 分钟前
C语言 查找一个字符在字符串中第i次出现的位置
c语言·算法
小六子成长记2 小时前
【C++】:搜索二叉树的模拟实现
数据结构·c++·算法
汉克老师2 小时前
GESP2025年9月认证C++二级真题与解析(编程题1(优美的数字))
c++·算法·整除·枚举算法·求余·拆数
Zevalin爱灰灰3 小时前
现代控制理论——第二章 系统状态空间表达式的解
线性代数·算法·现代控制
菜鸟233号3 小时前
力扣377 组合总和 Ⅳ java实现
java·数据结构·算法·leetcode
我是大咖3 小时前
二级指针与指针数组搭配
c语言·数据结构·算法
葫三生4 小时前
三生原理范畴语法表明中国哲学可为算法母语
人工智能·深度学习·算法·transformer
D_FW4 小时前
数据结构第五章:树与二叉树
数据结构·算法
WHS-_-20224 小时前
Tx and Rx IQ Imbalance Compensation for JCAS in 5G NR
javascript·算法·5g