实验二 基于MPI计算π值
一、实验题目:
用 MPI 集合通讯编程计算π的值。
二、实验目的
熟悉 MPI 编程的集合通讯调用,加深对其编程的理解。
三、实验环境
Linux Centos, 运行 MPI
四、程序设计
(一)源码:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <mpi.h>
//计算圆周率积分公式中的被积函数值
double f(double x) {
return 4.0 / (1.0 + x * x);
}
//梯形法则计算积分的近似值,base_length(梯形底边长度),num_intervals(梯形数量)和start(梯形起始位置)
double trapezoidal_integral(double base_length, int num_intervals, double start) {
double integral = (f(start) + f(start + num_intervals * base_length)) / 2.0;
for (int i = 1; i < num_intervals; i++) {
double x_i = start + i * base_length;
integral += f(x_i);
}
integral *= base_length;
return integral;
}
int main(int argc, char** argv) {
int rank, size;//进程等级、总进程数
int num_intervals = 0;
double base_length, local_integral, total_integral;
MPI_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORLD, &size);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
//等级为0,接收用户输入的区间数(梯形数),然后通过MPI_Bcast广播到所有进程。
if (rank == 0) {
printf("Enter the number of intervals: ");
fflush(stdout);
scanf("%d", &num_intervals);
}
MPI_Bcast(&num_intervals, 1, MPI_INT, 0, MPI_COMM_WORLD);
//计算梯形底边长度base_length。
base_length = 1.0 / (double)num_intervals;
//为每个进程分配本地梯形数量(local_num_intervals)和起始、结束位置(local_start和local_end)。
int local_num_intervals = num_intervals / size;
int local_start = rank * local_num_intervals;
int local_end = (rank == size - 1) ? num_intervals : (rank + 1) * local_num_intervals;
//计算本地起始位置对应的x值(local_base_start)。
double local_base_start = base_length * local_start;
//使用trapezoidal_integral函数计算每个进程的本地积分值(local_integral)。
local_integral = trapezoidal_integral(base_length, local_end - local_start, local_base_start);
MPI_Reduce(&local_integral, &total_integral, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);
//等级为0的进程输出最终估计的π值。
if (rank == 0) {
printf("Estimated value of Pi: %.15f\n", total_integral);
}
//结束MPI环境
MPI_Finalize();
return 0;
}
(二)整体逻辑:
代码的整体实现逻辑是使用MPI并行计算框架和梯形积分法来估计π值。以下是实现逻辑的详细步骤:
初始化MPI环境:使用MPI_Init函数初始化MPI环境。这是使用MPI进行并行计算的第一步。
获取进程信息:使用MPI_Comm_size获取总进程数(size),使用MPI_Comm_rank获取当前进程的等级(rank)。
输入区间数:在等级为0的进程中,从用户那里接收区间数(梯形数),然后通过MPI_Bcast广播到所有进程。
计算梯形底边长度:将1除以总区间数,得到梯形底边长度base_length。
分配本地梯形:为每个进程分配一定数量的梯形,计算每个进程的本地梯形数量(local_num_intervals),起始位置(local_start)和结束位置(local_end)。
计算本地积分值:根据每个进程的起始位置,计算本地梯形积分值(local_integral)。这一步使用了trapezoidal_integral函数来计算每个进程的本地积分值。
汇总积分值:使用MPI_Reduce函数将所有进程的本地积分值相加,得到总积分值(total_integral)。
输出结果:等级为0的进程输出最终估计的π值。
结束MPI环境:使用MPI_Finalize函数结束MPI环境。
整体实现逻辑的关键在于将梯形积分法应用于计算π的近似值,并利用MPI并行计算框架将任务分发到多个进程上,以提高计算效率。通过将总区间数(梯形数)分配给多个进程,可以在保持计算精度的同时减少单个进程的计算负担。
(三)关键函数分析:
(1)、double f(double x):这个函数计算圆周率积分公式中的被积函数值,即 4 / (1 + x^2)。这是一个数学公式,用于计算π的近似值。
(2)、double trapezoidal_integral(double base_length, int num_intervals, double start):这个函数使用梯形法则计算积分的近似值。它接收3个参数:base_length(梯形底边长度),num_intervals(梯形数量)和start(梯形起始位置)。函数首先计算起始点和结束点的函数值之和的一半,然后遍历每个梯形,计算梯形顶边对应点的函数值。最后将梯形和乘以底边长度,得到积分近似值。
(3)、主函数:
首先,初始化MPI环境并获取进程等级(rank)和总进程数(size)。
等级为0的进程接收用户输入的区间数(梯形数),然后通过MPI_Bcast广播到所有进程。
计算梯形底边长度base_length。
为每个进程分配本地梯形数量(local_num_intervals)和起始、结束位置(local_start和local_end)。
计算本地起始位置对应的x值(local_base_start)。
使用trapezoidal_integral函数计算每个进程的本地积分值(local_integral)。
通过MPI_Reduce将所有进程的本地积分值相加,得到总积分值(total_integral)。
等级为0的进程输出最终估计的π值。
结束MPI环境。
程序的整体逻辑是将梯形积分法应用于计算π的近似值。通过MPI并行计算,每个进程负责一部分梯形的计算,并将结果汇总以获得总的积分值。梯形数量越多,计算结果越接近真实的π值。
(四)、Linux命令:
Ls:列出当前目录中的所有文件或目录
Rm:删除文件或空目录
Rz:上传文件
mpicc -o pi_mpi 1.c 编译程序
mpirun -np 4 ./pi_mpi 运行程序
五、运行结果
mpirun -np 4 ./pi_mpi
六、实验心得
代码中程序编写技巧
- 包含头文件:代码中包含了一些标准头文件和MPI头文件,以便使用相关的函数和类型。例如,<stdio.h>、<stdlib.h>和<math.h>是标准头文件,<mpi.h>是MPI头文件。
- 积分函数定义:代码中定义了被积函数f(x),它返回一个double类型的结果。这个函数用于计算圆周率积分公式中的被积函数值。
- 梯形法则计算积分:代码中定义了trapezoidal_integral函数,它使用梯形法则来计算积分的近似值。它接受梯形底边长度、梯形数量和起始位置作为参数,并返回近似的积分值。
- MPI初始化:通过调用MPI_Init(&argc, &argv)来初始化MPI。argc和argv参数允许将命令行参数传递给MPI初始化函数。
- 获取进程等级和总进程数:使用MPI_Comm_size(MPI_COMM_WORLD, &size)和MPI_Comm_rank(MPI_COMM_WORLD, &rank)函数分别获取通信域中的进程数量和当前进程的等级。
- 输入参数广播:在等级为0的进程中,使用标准输入函数(如scanf)获取用户输入的梯形数量,并使用MPI_Bcast函数将其广播给所有进程。
- 任务分配:根据进程等级,计算每个进程的本地梯形数量、起始位置和结束位置。使用MPI_Bcast广播梯形底边长度给所有进程。
- 本地积分计算:在每个进程中,使用trapezoidal_integral函数计算本地积分值,使用本地起始位置和梯形数量计算本地积分范围。
- 全局积分汇总:使用MPI_Reduce函数将每个进程的本地积分值汇总到等级为0的进程中,使用MPI_SUM操作进行求和。
- 输出结果:在等级为0的进程中,输出最终估计的π值。
- 结束MPI环境:在程序的最后通过调用MPI_Finalize()函数来结束MPI。
函数使用技巧
- MPI初始化和结束:在程序的开始和结束分别调用
MPI_Init
和MPI_Finalize
函数来初始化和结束MPI环境。这确保了MPI库的正确使用,并且每个进程都在正确的时间点开始和结束MPI环境。 - 进程等级和总进程数获取:通过调用
MPI_Comm_size
和MPI_Comm_rank
函数分别获取通信域中的进程数量和当前进程的等级。这些信息对于进程间的任务分配和协调非常重要。 - 数据广播:使用
MPI_Bcast
函数将梯形数量和梯形底边长度从等级为0的进程广播给其他进程。这确保了所有进程都具有相同的参数值,以便进行一致的计算。 - 全局规约:使用
MPI_Reduce
函数将每个进程的本地积分值汇总到等级为0的进程中。使用MPI_SUM
操作进行求和。这允许在主进程中进行最终结果的计算和输出。 - 并行计算:在每个进程中使用
trapezoidal_integral
函数计算本地积分值。通过将工作分布给多个进程,实现并行计算,提高了计算效率。
可能出错的地方
- 输入合法性验证:代码中未对用户输入的梯形数量进行验证和范围检查。在实际应用中,可能需要添加对用户输入的合法性验证的代码。
- 梯形数量和进程数量的关系:代码中假设梯形数量是进程数量的整数倍,这样可以确保每个进程获得相等数量的梯形。如果梯形数量不能被进程数量整除,可能需要使用更复杂的任务划分策略。
- 积分方法的精确性:梯形法则是一种数值积分方法,其结果是对积分的近似值。对于更高精度的计算,可能需要使用其他数值积分方法。
- 并行性能:在这个简单的示例中,每个进程都进行了积分计算,然后将结果汇总到等级为0的进程中。这种并行计算方式可能不是最有效的,并且可能存在负载不均衡的问题。在实际应用中,可能需要进行更复杂的任务分配和负载平衡。
- 错误处理:MPI函数返回的错误代码可以用于检测并处理MPI调用中的错误。在实际的MPI程序中,通常需要对MPI函数的返回值进行检查,并根据错误码采取相应的错误处理措施。
在本次实验中,我们使用了MPI并行编程方法来估计π值。通过实验,我们学习了MPI的基本概念、函数和编程方法。以下是实验心得:
1.MPI的重要性:并行计算在高性能计算领域具有重要意义。MPI作为一种通用的并行编程方法,可以帮助我们在多个处理器上分发任务,以提高计算效率。
2.MPI函数的使用:在实验过程中,我们学习了MPI的基本函数,例如MPI_Init、MPI_Comm_size、MPI_Comm_rank、MPI_Bcast、MPI_Reduce等。这些函数有助于我们控制并行计算过程,分发任务并收集结果。
3.代码结构:通过将代码分解成不同的函数,如f和trapezoidal_integral,我们可以使代码结构更清晰,易于理解和维护。同时,将关键算法和MPI相关代码分离,有助于提高代码的可复用性。
4.调试与优化:在进行实验时,我发现了一个计算误差的问题,并通过仔细审查代码,成功解决了问题。这说明在进行并行编程时,要特别注意代码的正确性,确保计算结果的准确性。
5.性能与精度权衡:在实验中,我们学习了如何根据计算资源和期望精度选择合适的区间数。较大的区间数可以提高计算精度,但可能导致计算时间过长。因此,在实际应用中,需要根据需求合理选择参数,实现性能与精度之间的平衡。
总之,通过本次实验,我掌握了MPI并行编程的基本知识和技巧,加深了对并行计算的理解。在未来的工作中,我可以运用所学知识解决实际问题,提高计算效率。