实验三 高斯算法的MPI实现

一、实验题目:

用 MPI 技术设计实现高斯算法。

二、实验目的
熟悉 MPI 编程,加深对其编程的理解,并能够使用MPI进行程序设计。
三、实验环境
Linux Centos, 运行 MPI

四、程序设计

整体逻辑

检查命令行参数并获取n值。

初始化MPI环境。

获取当前进程的rank和进程总数。

记录开始时间。

计算每个进程的工作范围(local_start和local_end)。

计算当前进程的局部和(local_sum)。

使用MPI_Reduce函数将所有进程的local_sum归约到总和(total_sum)中。

记录结束时间。

如果当前进程是主进程(rank为0),打印计算结果和执行时间。

结束MPI环境。

关键函数及其相关用法

(1)MPI_Init()

用法:int MPI_Init(int *argc, char ***argv);

作用:初始化MPI环境。在使用MPI的程序中,此函数需要在所有其他MPI函数之前调用。

(2)MPI_Comm_rank()

用法:int MPI_Comm_rank(MPI_Comm comm, int *rank);

作用:获取当前进程在通信器(comm)中的编号(rank)。在程序中,我们将此编号作为进程的rank,并据此确定进程的工作范围。

(3)MPI_Comm_size()

用法:int MPI_Comm_size(MPI_Comm comm, int *size);

作用:获取通信器(comm)中的进程总数。在程序中,我们使用进程总数来计算每个进程的工作范围。

(4)MPI_Wtime()

用法:double MPI_Wtime(void);

作用:返回当前时间(以秒为单位),可用于测量代码的执行时间。在程序中,我们通过计算结束时间与开始时间的差值来计算总执行时间。

(5)MPI_Reduce()

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

作用:将所有进程的sendbuf中的数据进行归约操作(如求和、最大值等),并将结果存储在根进程(root)的recvbuf中。在程序中,我们使用MPI_SUM操作将所有进程的局部和(local_sum)归约到总和(total_sum)中。

(6)MPI_Finalize()

用法:int MPI_Finalize(void);

作用:结束MPI环境。在使用MPI的程序中,此函数需要在所有其他MPI函数之后调用。

代码实现

#include <stdio.h>

#include <stdlib.h>

#include <mpi.h>

int main(int argc, char *argv[]) {

    int rank, size;

    long long n, local_start, local_end, local_sum, total_sum;

    double start_time, end_time;

    // 检查命令行参数是否正确

    if (argc != 2) {

        printf("Usage: %s <n>\n", argv[0]);

        return 1;

    }

    // 从命令行参数获取n值

    n = atoll(argv[1]);

    // 初始化MPI环境

    MPI_Init(&argc, &argv);

    // 获取当前进程的rank(编号)

    MPI_Comm_rank(MPI_COMM_WORLD, &rank);

    // 获取进程总数

    MPI_Comm_size(MPI_COMM_WORLD, &size);

    // 记录开始时间

    start_time = MPI_Wtime();

    // 计算每个进程的工作范围(local_start和local_end)

    long long chunk_size = n / size;

    local_start = rank * chunk_size + 1;

    local_end = (rank == size - 1) ? n : (rank + 1) * chunk_size;

    // 计算当前进程的局部和(local_sum)

    local_sum = (local_start + local_end) * (local_end - local_start + 1) / 2;

    // 使用MPI_Reduce函数将所有进程的local_sum归约到总和(total_sum)中

    MPI_Reduce(&local_sum, &total_sum, 1, MPI_LONG_LONG_INT, MPI_SUM, 0, MPI_COMM_WORLD);

    // 记录结束时间

    end_time = MPI_Wtime();

    // 如果当前进程是主进程(rank为0),打印计算结果和执行时间

    if (rank == 0) {

        printf("The sum of integers from 1 to %lld is %lld.\n", n, total_sum);

        printf("Execution time: %lf seconds.\n", end_time - start_time);

    }

    // 结束MPI环境

    MPI_Finalize();

    return 0;

}

五、运行结果

编译:mpicc -o gauss_MPI  gauss_MPI.c

运行:mpirun -np 4 ./gauss_MPI <n>

<n>替换为要计算整数和的上限

六、实验心得

程序编写技巧

  1. 包含头文件:代码中包含了一些标准头文件和MPI头文件,以便使用相关的函数和类型。例如,<stdio.h><stdlib.h>是标准头文件,<mpi.h>是MPI头文件。
  2. 命令行参数处理:代码使用argcargv参数来接收命令行输入的参数。在这里,程序期望接收一个整数作为命令行参数,表示计算的范围。如果参数数量不正确,程序会打印用法信息并返回。
  3. MPI初始化和结束:通过调用MPI_InitMPI_Finalize函数来初始化和结束MPI环境。这确保了MPI库的正确使用,并且每个进程都在正确的时间点开始和结束MPI环境。
  4. 进程等级和总进程数获取:使用MPI_Comm_rankMPI_Comm_size函数分别获取通信域中的进程数量和当前进程的等级。这些信息对于进程间的任务分配和协调非常重要。
  5. 计算任务分配:通过计算每个进程的工作范围来划分计算任务。每个进程计算一部分整数的和,通过local_startlocal_end来确定本地计算范围。
  6. 局部和计算:每个进程计算自己的局部和,通过local_startlocal_end计算局部整数的和。
  7. 归约操作:使用MPI_Reduce函数将所有进程的局部和归约到总和中。这里使用了MPI_SUM操作,将所有进程的局部和相加,最终将结果放入主进程(rank为0)的total_sum变量中。
  8. 打印结果和执行时间:在主进程中,打印计算结果和执行时间信息。
  9. 记录执行时间:使用MPI_Wtime函数记录程序的开始时间和结束时间,计算执行时间。

函数使用技巧

  1. 命令行参数处理:通过接受命令行参数来灵活地获取用户输入的参数值。代码中使用argcargv参数来获取命令行参数的数量和值,并进行合法性检查。这种技巧使得程序可以通过命令行参数进行配置和定制。
  2. MPI初始化和结束:使用MPI_InitMPI_Finalize函数在程序的开始和结束分别初始化和结束MPI环境。这种技巧确保了MPI库的正确使用,保证了每个进程在正确的时间点开始和结束MPI环境。
  3. 进程等级和总进程数获取:使用MPI_Comm_rankMPI_Comm_size函数获取通信域中的进程数量和当前进程的等级。这些信息对于进程间的任务分配和协调非常重要。进程等级用于区分不同的进程,而进程数量用于确定任务划分的方式。
  4. 归约操作:通过使用MPI_Reduce函数将每个进程的局部求和归约到总和中。这种技巧利用了MPI提供的归约操作,通过对进程间的通信和协调来获得全局的结果。
  5. 执行时间测量:使用MPI_Wtime函数测量程序的执行时间。这个函数返回一个双精度浮点数,表示从MPI环境初始化到调用该函数的时间间隔。这种技巧允许程序在分布式环境中测量并报告程序的执行时间。

容易出错的地方

  1. 命令行参数处理:代码假设命令行参数的格式是正确的,即第一个参数是整数n。如果用户提供了不符合要求的参数格式,例如非整数参数或没有提供足够的参数,代码可能会出现错误。
  2. 工作范围划分:在将n划分为均匀的工作块时,代码假设n可以被进程数量整除,以获得均匀的工作划分。如果n不能被进程数量整除,可能会导致工作量不均衡,最后一个进程可能需要处理额外的工作。
  3. 归约操作的数据类型匹配:使用MPI_Reduce函数进行归约操作时,要确保传递的数据类型与归约操作的数据类型匹配。在这段代码中,使用MPI_LONG_LONG_INT作为数据类型,确保与local_sumtotal_sum的类型匹配。
  4. 错误处理:MPI函数返回的错误代码可以用于检测并处理MPI调用中的错误。在实际的MPI程序中,通常需要对MPI函数的返回值进行检查,并根据错误码采取相应的错误处理措施。在这段代码中,没有对MPI函数的返回值进行错误处理。
  5. 整数溢出:在这段代码中,使用了long long类型来存储大整数。但是,如果输入的n值超过了long long类型的表示范围,可能会导致整数溢出和计算错误。

心得体会

使用MPI时,确保已正确包含头文件#include <mpi.h>,并在编译时使用相应的MPI编译器(如mpicc)。

使用MPI_Wtime()测量执行时间。此函数提供了较高的时间精度,比使用clock()函数更为准确。

当处理命令行参数时,使用atoll()函数可以将字符串转换为长整型。在处理大整数时,这比使用atoi()函数更合适。

选择合适的chunk_size以平衡负载。在这个示例中,我们假设所有进程的计算速度相同,因此将任务平均分配给各个进程。然而,在实际应用中,可能需要根据具体情况动态调整chunk_size。

在编写MPI程序时,务必注意正确使用通信器(如MPI_COMM_WORLD),并确保在使用通信函数(如MPI_Reduce())时指定正确的根进程(root)和操作(如MPI_SUM)。

在并行计算中,确保各进程的局部和正确计算和归约。注意检查边界条件,如最后一个进程的工作范围。

  • 23
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值