【高性能计算笔记】

csdn推荐

第1章 - 高性能计算介绍 1. 概念:

高性能计算(High performance computing,缩写HPC):

指通常使用很多处理器(作为单个机器的一部分)或者某一集群中组织的几台计算机(作为单个计算资源操作)的计算系统和环境,执行一般个人电脑无法处理的大量资料与高速运算,其基本组成组件与个人电脑的概念无太大差异, 但规格与性能则强大许多

2. 性能衡量 2.1. HPL(High Performance Linpack) 2.1.1. 概念

现代高性能计算机通用的基准测试程序 。Linpack是用于测试高性能计算机系统浮点性能的基准测试程序。通过对高性能计算机采用高斯消元法求解一元N次稠密线性代数方程组的测试,评价高性能计算的浮点性能。  

2.1.2. 衡量性能单位:

每秒浮点运算次数 。

指每秒浮点运算次数,用来衡量硬件的性能(注意全部大写 )

浮点运算次数, 用来衡量算法/模型复杂度。

浮点运算包括所有涉及小数的运算,比整数运算更费时间。 现今大部分处理器中都有浮点运算器。因此每秒浮点运算次数所测量的实际上就是浮点运算器的执行速度。

2.2. HPCC(High Performance Computing Challenge)

是面向高性能计算机的综合测试程序包,能够测试高性能计算机系统多个方面的性能,包括处理器速度、存储访问速度和网络通信速度,对各种应用都有一定的代表性和参考价值

HPL简单易操作,结果直接明了,但不够精细,HPL评测标准显得过于粗略。HPCC充分弥补了HPL的一些缺点,对性能跨度非常大的各个方面都进行了充分的测试。

但也存在一些问题,那就是没有一个统一的结果评价标准,评价体系的构建不方便。

2.3. HPCG(High Performance Conjugate Gradients) ——高性能共轭梯度基准测试

是现在主要测试超算性能程序之一,也是 TOP500的一项重要指标。一般来讲HPCG的测试结果会比 HPL低很多,常常只有百分几。

高性能共轭梯度算法(HPCG) 所使用的计算模式与HPL 相比,更符合当前实际应用业务的特点,给出的测试结论对于HPC的发展更有参考价值  

2.4. 其他

3. 并行程序 3.1. 概念

能够使用多个核(处理器)一起工作来解决单一 问题

作用

求解更大的问题

3.2. 并行程序的编写(任务并行和数据并行)

将待解决问题的各个任务分配到各个核上执行

将待解决问题需要处理的数据分配到各个核上,每个核在分配的数据集上执行大致相似的操作.

3.3. 协调

各个核之间经常需要协调工作.

3.4. 常见概念

并行计算与分布式计算都属于并发计算

并行计算与分布式计算的区别

相似点:都是为了实现比较复杂的任务,将大的任务分解成小的任务,在 多台计算机上同时计算

计算机体系结构的发展及相应的编程模型/模式(单核、多核、分布式 和异构)

4. 习题 为求全局总和例子中的my_first_i和my_last_i推导一 个公式。需要注意的是:在循环中,应该给各个核 分配大致相同的计算元素。提示:先考虑n能被p整 除的情况。

解答:

当 nnn 能被 ppp 整除时:

对于每个处理器核心 ## 第1章 - 高性能计算介绍

1. 概念:

高性能计算(High performance computing,缩写HPC):

指通常使用很多处理器(作为单个机器的一部分)或者某一集群中组织的几台计算机(作为单个计算资源操作)的计算系统和环境,执行一般个人电脑无法处理的大量资料与高速运算,其基本组成组件与个人电脑的概念无太大差异, 但规格与性能则强大许多

2. 性能衡量 2.1. HPL(High Performance Linpack) 2.1.1. 概念

现代高性能计算机通用的基准测试程序 。Linpack是用于测试高性能计算机系统浮点性能的基准测试程序。通过对高性能计算机采用高斯消元法求解一元N次稠密线性代数方程组的测试,评价高性能计算的浮点性能。  

2.1.2. 衡量性能单位:

每秒浮点运算次数 。

指每秒浮点运算次数,用来衡量硬件的性能(注意全部大写 )

浮点运算次数, 用来衡量算法/模型复杂度。

浮点运算包括所有涉及小数的运算,比整数运算更费时间。 现今大部分处理器中都有浮点运算器。因此每秒浮点运算次数所测量的实际上就是浮点运算器的执行速度。

2.2. HPCC(High Performance Computing Challenge)

是面向高性能计算机的综合测试程序包,能够测试高性能计算机系统多个方面的性能,包括处理器速度、存储访问速度和网络通信速度,对各种应用都有一定的代表性和参考价值

HPL简单易操作,结果直接明了,但不够精细,HPL评测标准显得过于粗略。HPCC充分弥补了HPL的一些缺点,对性能跨度非常大的各个方面都进行了充分的测试。

但也存在一些问题,那就是没有一个统一的结果评价标准,评价体系的构建不方便。

2.3. HPCG(High Performance Conjugate Gradients) ——高性能共轭梯度基准测试

是现在主要测试超算性能程序之一,也是 TOP500的一项重要指标。一般来讲HPCG的测试结果会比 HPL低很多,常常只有百分几。

高性能共轭梯度算法(HPCG) 所使用的计算模式与HPL 相比,更符合当前实际应用业务的特点,给出的测试结论对于HPC的发展更有参考价值  

2.4. 其他

3. 并行程序 3.1. 概念

能够使用多个核(处理器)一起工作来解决单一 问题

作用

求解更大的问题

3.2. 并行程序的编写(任务并行和数据并行)

将待解决问题的各个任务分配到各个核上执行

将待解决问题需要处理的数据分配到各个核上,每个核在分配的数据集上执行大致相似的操作.

3.3. 协调

各个核之间经常需要协调工作.

3.4. 常见概念

并行计算与分布式计算都属于并发计算

并行计算与分布式计算的区别

相似点:都是为了实现比较复杂的任务,将大的任务分解成小的任务,在 多台计算机上同时计算

计算机体系结构的发展及相应的编程模型/模式(单核、多核、分布式 和异构)

4. 习题 为求全局总和例子中的my_first_i和my_last_i推导一 个公式。需要注意的是:在循环中,应该给各个核 分配大致相同的计算元素。提示:先考虑n能被p整 除的情况。

解答:

当 nnn 能被 ppp 整除时:

对于每个处理器核心

不能被

整除时:

对于前

个核心(即 (

):

对于后

个核心(即

全局总和例子的第一部分(每个核对分配给它的计算值求和),通常认为是数据并行的例子;而第一个求全局总和例 子的第二个部分(各个核将它们计算出的部分和发送给 master核,master核将这些部分和再累加求和),认为是任务 并行。 第二个树形结构的全局和例子(各个核使用树形结构累加它 们的部分和),是数据并行的例子还是任务并行的例子?为 什么?

该示例是任务和数据并行的组合。 在树结构全局求和的每个阶段,核心都在计算部分求和。 这可以看作是数据并行。 此外,在每个阶段,有两种类型的任务。 一些内核正在发送它们的总和,而一些内核正在接收另一个内核的部分总和。 这可以看作是任务并行。

第二章 - 并行硬件和并行软件 1. 基本概念 1.1. 冯诺依曼结构

中央处理器(CPU) 关键术语 主存 冯诺依曼瓶颈 1.2. 缓存

高速缓冲存储器简称缓存

1.3. 进程

正在运行的程序的实例

2. 并行硬件 2.1. Flynn经典分类

从处理器的角度进行分类:按照处理器能够同时管理的指令流数目和数据流数目来对系统分类。

2.1.1. 单指令单数据流(SISD)

是一个串行的计算机系统

2.1.2. 单指令多数据流(SIMD)

是并行系统。

划分工作,并进行迭代循环处理。

Example

2.1.3. 多指令单数据流(MISD) 2.1.4. 多指令多数据流(MIMD) 2.2. 内存结构分类

2.2.1. 共享内存系统 一致内存访问(UMA):互连网络将所有的处理器直接连到主存。

每个核访问内存任意位置的时间相等 。如:对称多处理器(SMP)

非一致内存访问(NUMA):将每个处理器直接连到单独的一 块内存上,然后通过处理器中内置的特殊硬件使得各个处理器可以访问内存中的其它块。

UMA与NUMA系统的特点: 2.2.2. 分布式内存系统

网络提供一种基础架构,使地理上分布的计算机大型 网络转换成一个分布式内存系统。

2.3. 互连网络

两类: ✓共享内存互连 ✓分布式内存互连

3. 并行软件

分布式内存程序:

➢分布式内存编程模型(MPI、MapReduce)

➢共享内存编程模型(Pthreads、OpenMP)

➢异构系统的编程模型(CUDA、OpenCL)

第三章 - MPI 1. MPI简介

MPI(Message-Passing Interface)

分布式内存编程模型

编译: mpicc-g -Wall -o mpi_hello mpi_hello.c 执行: mpiexec -n 1 ./mpi_hello 2. MPI点对点通信 2.1. 基本函数

MPI_Recv(buf,count,datatype,source,tag,comm,status)

发送函数: 阻塞和缓冲

接收函数: 总是阻塞的

2.2. 基本概念 通信子

通信子是所有可以进行相互通信的进程的集合。

MPI_COMM_WORLD是MPI预定义的常量,是一个 包含所有用户启动进程的通信子。(MPI_Init)

一个进程可以属于多个通信子, 进程在不同通信子中的标识号可以不同

通配符(Wildcard)

通配符允许消息传递操作中灵活地匹配消息源或标签。常用的通配符有:

#include 
#include 
#include 
double f(double x) {
    return x * x; // 被积函数 x^2
}
double Trap(double local_a, double local_b, int local_n, double h) {
    double integral = (f(local_a) + f(local_b)) / 2.0;
    for (int i = 1; i <= local_n - 1; i++) {
        double x = local_a + i * h;
        integral += f(x);
    }
    integral *= h;
    return integral;
}
int main(int argc, char* argv[]) {
    int my_rank, comm_sz, n = 1024, local_n;
    double a = -2.0, b = 2.0 h, local_a, local_b;
	double local_int, total_int;
    int source;
    MPI_Init(NULL, NULL);
    MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
    MPI_Comm_size(MPI_COMM_WORLD, &comm_sz);
    h = (b - a) / n;
    local_n = n / comm_sz;
    local_a = a + my_rank * local_n * h;
    local_b = local_a + local_n * h;
    local_int = Trap(local_a, local_b, local_n, h);
    if (my_rank != 0) {
        MPI_Send(&local_integral, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);
    } else {
        total_integral = local_int;
        for (int source = 1; source < comm_sz; source++) {
            MPI_Recv(&local_integral, 1, MPI_DOUBLE, source, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
            total_integral += local_integral;
        }
        printf("With n = %d trapezoids, integral from %f to %f = %fn", n, a, b, total_integral);
    }
    MPI_Finalize();
    return 0;
}

3. MPI 集合通信( Collective communication ) 3.1. 概述

功能:

3.2. 通信方式——广播规约 3.2.1. 广播

广播: 一个进程的数据被发送到通信子中的所有进程,即 广播 (一对多)

广播函数

 MPI_Bcast(buffer,count,datatype,root,comm)

参数解释

buffer(void *) count(int) datatype(MPI_Datatype) root(int) comm(MPI_Comm)

int main(int argc, char* argv[]) {
    int my_rank, comm_sz;
    int data;
    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
    MPI_Comm_size(MPI_COMM_WORLD, &comm_sz);
    if (my_rank == 0) {
        // Root process initializes the data
        data = 42;
        printf("Process %d broadcasting data: %dn", my_rank, data);
    } else {
        // Other processes initialize data with a different value
        data = -1;
    }
    // Broadcast the data from root process (rank 0) to all processes
    MPI_Bcast(&data, 1, MPI_INT, 0, MPI_COMM_WORLD);
    // All processes print the received data
    printf("Process %d received data: %dn", my_rank, data);
    MPI_Finalize();
    return 0;
}

3.2.2. 规约

规约: (多对一)

除完成消息传递功能之外,还能对所传递的数 据进行一定的操作或运算 在进行通信的同时完成一定的计算

规约函数

int MPI_Reduce(const void *sendbuf, void *recvbuf, int count, MPI_Datatype datatype, MPI_Op op, int root, MPI_Comm comm);

参数解释

sendbuf(const void *) recvbuf(void *) count(int) datatype(MPI_Datatype) op(MPI_Op) root(int) comm(MPI_Comm)

int main(int argc, char* argv[]) {
    int my_rank, comm_sz;
    int local_data;
    int global_sum;
    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
    MPI_Comm_size(MPI_COMM_WORLD, &comm_sz);
    // 每个进程的数据
    local_data = my_rank + 1;
    // 进行归约操作,求和
    MPI_Reduce(&local_data, &global_sum, 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD);
    // 只有根进程(rank 0)输出结果
    if (my_rank == 0) {
        printf("The sum of all ranks is %dn", global_sum);
    }
    MPI_Finalize();
    return 0;
}

#include 
#include 
#include 
double f(double x) {
    return x * x; // 被积函数 x^2
}
double Trap(double local_a, double local_b, int local_n, double h) {
    double integral = (f(local_a) + f(local_b)) / 2.0;
    for (int i = 1; i <= local_n - 1; i++) {
        double x = local_a + i * h;
        integral += f(x);
    }
    integral *= h;
    return integral;
}
int main(int argc, char* argv[]) {
    int my_rank, comm_sz, n, local_n;
    double a, b, h, local_a, local_b;
	double local_int, total_int;
    int source;
    MPI_Init(NULL, NULL);
    MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
    MPI_Comm_size(MPI_COMM_WORLD, &comm_sz);
	
	if (my_rank == 0) {    
        n = 1000;
        a = -2.0; 
        b = 2.0;  
    }
	
    MPI_Bcast(&a, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD);
    MPI_Bcast(&b, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD);
    MPI_Bcast(&n, 1, MPI_INT, 0, MPI_COMM_WORLD);
    h = (b - a) / n;
    local_n = n / comm_sz;
    local_a = a + my_rank * local_n * h;
    local_b = local_a + local_n * h;
    local_int = Trap(local_a, local_b, local_n, h);
	
	MPI_Reduce(&local_int, &total_int, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);
    if (my_rank == 0) {
        printf("With n = %d trapezoids, integral from %f to %f = %fn", n, a, b, total_int);
    }
    MPI_Finalize();
    return 0;
}	

3.2.3. 集合通信与点对点通信的区别

所有进程仍需要传递一个与recvbuf相对应的实际 参数,即使它的值是NULL

点对点通信通过标签和通信子匹配,集合通信通 过通信子和调用顺序匹配

Time 1:

b = a + c + c = 1 + 4 + 6 = 11

Time 2:

d = 2 + 3 + 5 = 10

3.2.4. 规约—广播(N—N)

#include 
#include 
int main(int argc, char* argv[]) {
    MPI_Init(&argc, &argv);
    int my_rank, comm_sz;
    MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
    MPI_Comm_size(MPI_COMM_WORLD, &comm_sz);
    int local_value = my_rank + 1; // 每个进程的局部值
    int total_sum = 0;
    MPI_Allreduce(&local_value, &total_sum, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD);
    printf("Process %d: local_value = %d, total_sum = %dn", my_rank, local_value, total_sum);
    MPI_Finalize();
    return 0;
}

3.3. 通信方式——散射聚集 3.3.1. 散射

散射: 一对多

MPI_Scatter可以使0号进程读取整个向量,但是 只将分量发送给需要分量的其他进程

函数

MPI_Scatter(sendbuf,sendcount,sendtype,recvbuf,recvcount,recvty pe,src_proc,comm)

参数解释

int main(int argc, char* argv[]) {
    int comm_sz;  // 进程总数
    int my_rank;  // 当前进程的 rank
    int *sendbuf = NULL;
    int recvbuf;
    int sendcount = 1;
    int recvcount = 1;
    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
    MPI_Comm_size(MPI_COMM_WORLD, &comm_sz);
    // 根进程初始化发送缓冲区
    if (my_rank == 0) {
        sendbuf = (int *)malloc(comm_sz * sizeof(int));
        for (int i = 0; i < comm_sz; i++) {
            sendbuf[i] = i + 1;  // 或者任意其他初始化方式
        }
    }
    // 分发数据,每个进程接收一个整数
    MPI_Scatter(sendbuf, sendcount, MPI_INT, &recvbuf, recvcount, MPI_INT, 0, MPI_COMM_WORLD);
    // 打印接收到的数据
    printf("Process %d received data %dn", my_rank, recvbuf);
    // 根进程释放发送缓冲区
    if (my_rank == 0) {
        free(sendbuf);
    }
    MPI_Finalize();
    return 0;
}

Process 0 received data 1

Process 1 received data 2

Process 2 received data 3

Process 3 received data 4

3.3.2. 聚集

多对一

int MPI_Gather(const void *sendbuf, int sendcount, MPI_Datatype sendtype,
               void *recvbuf, int recvcount, MPI_Datatype recvtype,
               int root, MPI_Comm comm); 

int main(int argc, char* argv[]) {
    int comm_sz;  // 进程总数
    int my_rank;  // 当前进程的 rank
    int sendbuf;  // 每个进程要发送的数据
    int *recvbuf = NULL;
    int sendcount = 1;
    int recvcount = 1;
    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
    MPI_Comm_size(MPI_COMM_WORLD, &comm_sz);
    // 每个进程初始化发送缓冲区
    sendbuf = my_rank + 1;
    // 根进程分配接收缓冲区
    if (my_rank == 0) {
        recvbuf = (int *)malloc(comm_sz * sizeof(int));
    }
    // 收集数据
    MPI_Gather(&sendbuf, sendcount, MPI_INT, recvbuf, recvcount, MPI_INT, 0, MPI_COMM_WORLD);
    // 根进程打印接收到的数据
    if (my_rank == 0) {
        printf("Root process received data:n");
        for (int i = 0; i < comm_sz; i++) {
            printf("%d ", recvbuf[i]);
        }
        printf("n");
        free(recvbuf);
    }
    MPI_Finalize();
    return 0;
}

4. 性能评估 4.1. 计时功能 double MPI_Wtime(void);

MPI_Wtime 返回一个双精度浮点数,表示自某个时间点(通常是程序开始执行时)到当前时间的秒数。

int main(int argc, char* argv[]) {
    MPI_Init(&argc, &argv);
    double start_time, end_time;
    // 获取开始时间
    start_time = MPI_Wtime();
    // 模拟一些工作,假设用 sleep(2) 代表需要测量的代码段
    // 在实际代码中应使用需要测量的代码段
    sleep(2);
    // 获取结束时间
    end_time = MPI_Wtime();
    // 计算并打印执行时间
    printf("Execution time: %f secondsn", end_time - start_time);
    MPI_Finalize();
    return 0;
}

MPI_Wtick

MPI_Wtick 返回一个双精度浮点数,表示计时器的分辨率(以秒为单位)。

4.2. 安全通信

第四章 Pthreads 1. 进程、线程 进程、线程

2. Pthreads( 共享内存编程模型 )

POSIX线程库也称为Pthreads线程库,它定义了一套多线程编程的应用程序编程接口 。

2.1. 编译

gcc−g −Wall −o pth_hellopth_hello.c−lpthread

参数解释

gcc: -g: -Wall: -o pth_hello: pth_hello.c: -lpthread:

第五章 - 用OpenMP进行共享内存编程 1. OpenMP简介

OpenMP是一个针对共享内存编程的API。

2. 编译指导语句 2.1. 并行执行模式:

"Fork/Join”方式:

#include 
#include 
int main() {
    int nthreads;
    // Fork: 主线程创建多个子线程
    #pragma omp parallel
    {
        int tid = omp_get_thread_num(); // 获取线程编号
        nthreads = omp_get_num_threads(); // 获取总线程数
        printf("Hello from thread %d of %dn", tid, nthreads);
    }
    // Join: 所有子线程完成并行区域,汇合到主线程
    printf("All threads have finished.n");
    return 0;
}

2.2. 程序编写 2.2.1. 预处理指令 2.2.2. OpenMP编译指导语句格式

#pragmaompparallel[clause…]new-line Structured-block

#include 
#include 
#include 
int main(int argc, char *argv[]) {
    int i;
    int thread_count = strtol(argv[1], NULL, 10);
    #pragma omp parallel for num_threads(thread_count)
    for (i = 0; i < 10; i++) {
        printf("i = %dn", i);
    }
    printf("Finished.n");
    return 0;
}

2.2.3. 编译运行

gcc -g -Wall -fopenmp -o omp_example omp_example.c

-fopenmp:

# 分配四个 threads
./omp_example 4  

2.2.4. helloworld 程序

OpenMP 指令

#pragma omp parallel

OpenMP 子句

# pragma omp parallel num_threads(thread_count)

OpenMP 线程

OpenMP函数

 int omp_get_thread_num(void);  

 int omp_get_num_threads(void) 

注意因为标准输出被所有线程共享,所以每个 线程都能够执行printf语句,打印它的线程编号 和线程数。由于对标准输出的访问没有调度, 因此线程打印它们结果的实际顺序不确定

2.3. OpenMP 编程 2.3.1. critical指令

引起竞争的代码,称为临界区

竞争:多个线程试图访问并更新一个共享资源

因此需要一些机制来确保一次只有一个线程执行 临界区,这种操作,我们称为互斥。

2.3.2. 变量作用域

2.3.3. 规约子句 reduction

注意:每个线程的私有拷贝值global_result初始化为0。

2.3.4. parallel for

for指令是用来将一个for循环分配到多个线程中执行。

for指令的使用方式:

parallel for指令

第二章 - 并行硬件和并行软件 1. 基本概念 1.1. 冯诺依曼结构

中央处理器(CPU) 关键术语 主存 冯诺依曼瓶颈 1.2. 缓存

高速缓冲存储器简称缓存

1.3. 进程

正在运行的程序的实例

2. 并行硬件 2.1. Flynn经典分类

从处理器的角度进行分类:按照处理器能够同时管理的指令流数目和数据流数目来对系统分类。

2.1.1. 单指令单数据流(SISD)

是一个串行的计算机系统

2.1.2. 单指令多数据流(SIMD)

是并行系统。

划分工作,并进行迭代循环处理。

Example

2.1.3. 多指令单数据流(MISD) 2.1.4. 多指令多数据流(MIMD) 2.2. 内存结构分类

2.2.1. 共享内存系统 一致内存访问(UMA):互连网络将所有的处理器直接连到主存。

每个核访问内存任意位置的时间相等 。如:对称多处理器(SMP)

非一致内存访问(NUMA):将每个处理器直接连到单独的一 块内存上,然后通过处理器中内置的特殊硬件使得各个处理器可以访问内存中的其它块。

UMA与NUMA系统的特点: 2.2.2. 分布式内存系统

网络提供一种基础架构,使地理上分布的计算机大型 网络转换成一个分布式内存系统。

2.3. 互连网络

两类: ✓共享内存互连 ✓分布式内存互连

3. 并行软件

分布式内存程序:

➢分布式内存编程模型(MPI、MapReduce)

➢共享内存编程模型(Pthreads、OpenMP)

➢异构系统的编程模型(CUDA、OpenCL)

第三章 - MPI 1. MPI简介

MPI(Message-Passing Interface)

分布式内存编程模型

编译: mpicc-g -Wall -o mpi_hello mpi_hello.c 执行: mpiexec -n 1 ./mpi_hello 2. MPI点对点通信 2.1. 基本函数

MPI_Recv(buf,count,datatype,source,tag,comm,status)

发送函数: 阻塞和缓冲

接收函数: 总是阻塞的

2.2. 基本概念 通信子

通信子是所有可以进行相互通信的进程的集合。

MPI_COMM_WORLD是MPI预定义的常量,是一个 包含所有用户启动进程的通信子。(MPI_Init)

一个进程可以属于多个通信子, 进程在不同通信子中的标识号可以不同

通配符(Wildcard)

通配符允许消息传递操作中灵活地匹配消息源或标签。常用的通配符有:

#include 
#include 
#include 
double f(double x) {
    return x * x; // 被积函数 x^2
}
double Trap(double local_a, double local_b, int local_n, double h) {
    double integral = (f(local_a) + f(local_b)) / 2.0;
    for (int i = 1; i <= local_n - 1; i++) {
        double x = local_a + i * h;
        integral += f(x);
    }
    integral *= h;
    return integral;
}
int main(int argc, char* argv[]) {
    int my_rank, comm_sz, n = 1024, local_n;
    double a = -2.0, b = 2.0 h, local_a, local_b;
	double local_int, total_int;
    int source;
    MPI_Init(NULL, NULL);
    MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
    MPI_Comm_size(MPI_COMM_WORLD, &comm_sz);
    h = (b - a) / n;
    local_n = n / comm_sz;
    local_a = a + my_rank * local_n * h;
    local_b = local_a + local_n * h;
    local_int = Trap(local_a, local_b, local_n, h);
    if (my_rank != 0) {
        MPI_Send(&local_integral, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);
    } else {
        total_integral = local_int;
        for (int source = 1; source < comm_sz; source++) {
            MPI_Recv(&local_integral, 1, MPI_DOUBLE, source, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
            total_integral += local_integral;
        }
        printf("With n = %d trapezoids, integral from %f to %f = %fn", n, a, b, total_integral);
    }
    MPI_Finalize();
    return 0;
}

3. MPI 集合通信( Collective communication ) 3.1. 概述

功能:

3.2. 通信方式——广播规约 3.2.1. 广播

广播: 一个进程的数据被发送到通信子中的所有进程,即 广播 (一对多)

广播函数

 MPI_Bcast(buffer,count,datatype,root,comm)

参数解释

buffer(void *) count(int) datatype(MPI_Datatype) root(int) comm(MPI_Comm)

int main(int argc, char* argv[]) {
    int my_rank, comm_sz;
    int data;
    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
    MPI_Comm_size(MPI_COMM_WORLD, &comm_sz);
    if (my_rank == 0) {
        // Root process initializes the data
        data = 42;
        printf("Process %d broadcasting data: %dn", my_rank, data);
    } else {
        // Other processes initialize data with a different value
        data = -1;
    }
    // Broadcast the data from root process (rank 0) to all processes
    MPI_Bcast(&data, 1, MPI_INT, 0, MPI_COMM_WORLD);
    // All processes print the received data
    printf("Process %d received data: %dn", my_rank, data);
    MPI_Finalize();
    return 0;
}

3.2.2. 规约

规约: (多对一)

除完成消息传递功能之外,还能对所传递的数 据进行一定的操作或运算 在进行通信的同时完成一定的计算

规约函数

int MPI_Reduce(const void *sendbuf, void *recvbuf, int count, MPI_Datatype datatype, MPI_Op op, int root, MPI_Comm comm);

参数解释

sendbuf(const void *) recvbuf(void *) count(int) datatype(MPI_Datatype) op(MPI_Op) root(int) comm(MPI_Comm)

int main(int argc, char* argv[]) {
    int my_rank, comm_sz;
    int local_data;
    int global_sum;
    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
    MPI_Comm_size(MPI_COMM_WORLD, &comm_sz);
    // 每个进程的数据
    local_data = my_rank + 1;
    // 进行归约操作,求和
    MPI_Reduce(&local_data, &global_sum, 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD);
    // 只有根进程(rank 0)输出结果
    if (my_rank == 0) {
        printf("The sum of all ranks is %dn", global_sum);
    }
    MPI_Finalize();
    return 0;
}

#include 
#include 
#include 
double f(double x) {
    return x * x; // 被积函数 x^2
}
double Trap(double local_a, double local_b, int local_n, double h) {
    double integral = (f(local_a) + f(local_b)) / 2.0;
    for (int i = 1; i <= local_n - 1; i++) {
        double x = local_a + i * h;
        integral += f(x);
    }
    integral *= h;
    return integral;
}
int main(int argc, char* argv[]) {
    int my_rank, comm_sz, n, local_n;
    double a, b, h, local_a, local_b;
	double local_int, total_int;
    int source;
    MPI_Init(NULL, NULL);
    MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
    MPI_Comm_size(MPI_COMM_WORLD, &comm_sz);
	
	if (my_rank == 0) {    
        n = 1000;
        a = -2.0; 
        b = 2.0;  
    }
	
    MPI_Bcast(&a, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD);
    MPI_Bcast(&b, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD);
    MPI_Bcast(&n, 1, MPI_INT, 0, MPI_COMM_WORLD);
    h = (b - a) / n;
    local_n = n / comm_sz;
    local_a = a + my_rank * local_n * h;
    local_b = local_a + local_n * h;
    local_int = Trap(local_a, local_b, local_n, h);
	
	MPI_Reduce(&local_int, &total_int, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);
    if (my_rank == 0) {
        printf("With n = %d trapezoids, integral from %f to %f = %fn", n, a, b, total_int);
    }
    MPI_Finalize();
    return 0;
}	

3.2.3. 集合通信与点对点通信的区别

所有进程仍需要传递一个与recvbuf相对应的实际 参数,即使它的值是NULL

点对点通信通过标签和通信子匹配,集合通信通 过通信子和调用顺序匹配

Time 1:

b = a + c + c = 1 + 4 + 6 = 11

Time 2:

d = 2 + 3 + 5 = 10

3.2.4. 规约—广播(N—N)

#include 
#include 
int main(int argc, char* argv[]) {
    MPI_Init(&argc, &argv);
    int my_rank, comm_sz;
    MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
    MPI_Comm_size(MPI_COMM_WORLD, &comm_sz);
    int local_value = my_rank + 1; // 每个进程的局部值
    int total_sum = 0;
    MPI_Allreduce(&local_value, &total_sum, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD);
    printf("Process %d: local_value = %d, total_sum = %dn", my_rank, local_value, total_sum);
    MPI_Finalize();
    return 0;
}

3.3. 通信方式——散射聚集 3.3.1. 散射

散射: 一对多

MPI_Scatter可以使0号进程读取整个向量,但是 只将分量发送给需要分量的其他进程

函数

MPI_Scatter(sendbuf,sendcount,sendtype,recvbuf,recvcount,recvty pe,src_proc,comm)

参数解释

int main(int argc, char* argv[]) {
    int comm_sz;  // 进程总数
    int my_rank;  // 当前进程的 rank
    int *sendbuf = NULL;
    int recvbuf;
    int sendcount = 1;
    int recvcount = 1;
    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
    MPI_Comm_size(MPI_COMM_WORLD, &comm_sz);
    // 根进程初始化发送缓冲区
    if (my_rank == 0) {
        sendbuf = (int *)malloc(comm_sz * sizeof(int));
        for (int i = 0; i < comm_sz; i++) {
            sendbuf[i] = i + 1;  // 或者任意其他初始化方式
        }
    }
    // 分发数据,每个进程接收一个整数
    MPI_Scatter(sendbuf, sendcount, MPI_INT, &recvbuf, recvcount, MPI_INT, 0, MPI_COMM_WORLD);
    // 打印接收到的数据
    printf("Process %d received data %dn", my_rank, recvbuf);
    // 根进程释放发送缓冲区
    if (my_rank == 0) {
        free(sendbuf);
    }
    MPI_Finalize();
    return 0;
}

Process 0 received data 1

Process 1 received data 2

Process 2 received data 3

Process 3 received data 4

3.3.2. 聚集

多对一

int MPI_Gather(const void *sendbuf, int sendcount, MPI_Datatype sendtype,
               void *recvbuf, int recvcount, MPI_Datatype recvtype,
               int root, MPI_Comm comm); 

int main(int argc, char* argv[]) {
    int comm_sz;  // 进程总数
    int my_rank;  // 当前进程的 rank
    int sendbuf;  // 每个进程要发送的数据
    int *recvbuf = NULL;
    int sendcount = 1;
    int recvcount = 1;
    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
    MPI_Comm_size(MPI_COMM_WORLD, &comm_sz);
    // 每个进程初始化发送缓冲区
    sendbuf = my_rank + 1;
    // 根进程分配接收缓冲区
    if (my_rank == 0) {
        recvbuf = (int *)malloc(comm_sz * sizeof(int));
    }
    // 收集数据
    MPI_Gather(&sendbuf, sendcount, MPI_INT, recvbuf, recvcount, MPI_INT, 0, MPI_COMM_WORLD);
    // 根进程打印接收到的数据
    if (my_rank == 0) {
        printf("Root process received data:n");
        for (int i = 0; i < comm_sz; i++) {
            printf("%d ", recvbuf[i]);
        }
        printf("n");
        free(recvbuf);
    }
    MPI_Finalize();
    return 0;
}

4. 性能评估 4.1. 计时功能 double MPI_Wtime(void);

MPI_Wtime 返回一个双精度浮点数,表示自某个时间点(通常是程序开始执行时)到当前时间的秒数。

int main(int argc, char* argv[]) {
    MPI_Init(&argc, &argv);
    double start_time, end_time;
    // 获取开始时间
    start_time = MPI_Wtime();
    // 模拟一些工作,假设用 sleep(2) 代表需要测量的代码段
    // 在实际代码中应使用需要测量的代码段
    sleep(2);
    // 获取结束时间
    end_time = MPI_Wtime();
    // 计算并打印执行时间
    printf("Execution time: %f secondsn", end_time - start_time);
    MPI_Finalize();
    return 0;
}

MPI_Wtick

MPI_Wtick 返回一个双精度浮点数,表示计时器的分辨率(以秒为单位)。

4.2. 安全通信

第四章 Pthreads 1. 进程、线程 进程、线程

2. Pthreads( 共享内存编程模型 )

POSIX线程库也称为Pthreads线程库,它定义了一套多线程编程的应用程序编程接口 。

2.1. 编译

gcc−g −Wall −o pth_hellopth_hello.c−lpthread

参数解释

gcc: -g: -Wall: -o pth_hello: pth_hello.c: -lpthread:

第五章 - 用OpenMP进行共享内存编程 1. OpenMP简介

OpenMP是一个针对共享内存编程的API。

2. 编译指导语句 2.1. 并行执行模式:

"Fork/Join”方式:

#include 
#include 
int main() {
    int nthreads;
    // Fork: 主线程创建多个子线程
    #pragma omp parallel
    {
        int tid = omp_get_thread_num(); // 获取线程编号
        nthreads = omp_get_num_threads(); // 获取总线程数
        printf("Hello from thread %d of %dn", tid, nthreads);
    }
    // Join: 所有子线程完成并行区域,汇合到主线程
    printf("All threads have finished.n");
    return 0;
}

2.2. 程序编写 2.2.1. 预处理指令 2.2.2. OpenMP编译指导语句格式

#pragmaompparallel[clause…]new-line Structured-block

#include 
#include 
#include 
int main(int argc, char *argv[]) {
    int i;
    int thread_count = strtol(argv[1], NULL, 10);
    #pragma omp parallel for num_threads(thread_count)
    for (i = 0; i < 10; i++) {
        printf("i = %dn", i);
    }
    printf("Finished.n");
    return 0;
}

2.2.3. 编译运行

gcc -g -Wall -fopenmp -o omp_example omp_example.c

-fopenmp:

# 分配四个 threads
./omp_example 4  

2.2.4. helloworld 程序

OpenMP 指令

#pragma omp parallel

OpenMP 子句

# pragma omp parallel num_threads(thread_count)

OpenMP 线程

OpenMP函数

 int omp_get_thread_num(void);  

 int omp_get_num_threads(void) 

注意因为标准输出被所有线程共享,所以每个 线程都能够执行printf语句,打印它的线程编号 和线程数。由于对标准输出的访问没有调度, 因此线程打印它们结果的实际顺序不确定

2.3. OpenMP 编程 2.3.1. critical指令

引起竞争的代码,称为临界区

竞争:多个线程试图访问并更新一个共享资源

因此需要一些机制来确保一次只有一个线程执行 临界区,这种操作,我们称为互斥。

2.3.2. 变量作用域

2.3.3. 规约子句 reduction

注意:每个线程的私有拷贝值global_result初始化为0。

2.3.4. parallel for

for指令是用来将一个for循环分配到多个线程中执行。

for指令的使用方式:

parallel for指令

文章来源:https://blog.csdn.net/weixin_64632836/article/details/139935256



微信扫描下方的二维码阅读本文

© 版权声明
THE END
喜欢就支持一下吧
点赞14 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容