实验五 高斯算法的OpenMP实现

一、实验题目:

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

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

四、程序设计

整体逻辑

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

记录开始时间。

创建并行区域。

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

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

使用critical指令保护对total_sum的更新,确保互斥访问。

结束并行区域。

记录结束时间。

打印计算结果和执行时间。

关键函数及其相关用法

(1)omp_get_wtime()

用法:double omp_get_wtime(void);

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

(2)omp_get_thread_num()

用法:int omp_get_thread_num(void);

作用:返回调用线程在其所属并行区域中的唯一ID(从0开始)。在程序中,我们将此ID作为线程的rank,并据此确定线程的工作范围。

(3)omp_get_num_threads()

用法:int omp_get_num_threads(void);

作用:返回当前并行区域中的线程总数。在程序中,我们使用线程总数来计算每个线程的工作范围。

代码实现

#include <stdio.h>

#include <stdlib.h>

#include <omp.h>

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

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

    if (argc != 2) {

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

        return 1;

    }

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

    long long n = atoll(argv[1]);

    long long total_sum = 0;

    double start_time, end_time;

    // 记录开始时间

    start_time = omp_get_wtime();

    // 创建并行区域

#pragma omp parallel

    {

        long long local_start, local_end, local_sum;

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

        int rank = omp_get_thread_num();

        // 获取线程总数

        int size = omp_get_num_threads();

        // 计算每个线程的工作范围(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;

        // 使用critical指令保护对total_sum的更新,确保互斥访问

#pragma omp critical

        total_sum += local_sum;

    }

    // 记录结束时间

    end_time = omp_get_wtime();

    // 打印计算结果和执行时间

    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);

    return 0;

}

五、运行结果

编译:gcc -fopenmp -o gauss_OpenMP gauss_OpenMP.c

运行:./gauss_OpenMP <n>

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

六、实验心得

程序编写技巧

  1. 包含头文件:代码中包含了一些标准头文件和OpenMP头文件,以便使用相关的函数和类型。例如,<stdio.h><stdlib.h>是标准头文件,<omp.h>OpenMP头文件。
  2. 命令行参数处理:通过命令行参数获取整数n的值,并检查参数的正确性。代码假设命令行参数的格式是正确的,即第一个参数是整数n。如果用户提供了不符合要求的参数格式,例如非整数参数或没有提供足够的参数,代码可能会出现错误。
  3. 并行区域:使用#pragma omp parallel创建并行区域。这个指令将下面的代码块标记为并行执行。在这段代码中,所有并行线程都会执行标记的代码块。
  4. 线程编号和线程总数获取:使用omp_get_thread_numomp_get_num_threads函数分别获取当前线程的编号(rank)和线程的总数。这些信息对于线程间的任务分配和协调非常重要。
  5. 工作范围划分:根据线程编号,计算每个线程的工作范围(local_startlocal_end)。使用整除运算符将n划分为均匀的工作块,确保每个线程负责一部分工作。
  6. 局部求和计算:在每个线程中,根据工作范围计算当前线程的局部求和(local_sum)。这里使用了求和公式来计算给定范围内整数的和。
  7. 互斥访问:使用#pragma omp critical指令来保护对共享变量total_sum的更新。这确保了每个线程在更新total_sum时的互斥访问,避免了竞争条件。
  8. 执行时间测量:使用omp_get_wtime函数测量程序的执行时间。这个函数返回一个双精度浮点数,表示从计时器启动到调用该函数的时间间隔。这种技巧允许程序在并行环境中测量并报告程序的执行时间。
  9. 打印结果和执行时间:在主线程中,打印计算结果和程序的执行时间。这里使用了printf函数来格式化和输出结果。

函数使用技巧

  1. 命令行参数处理:代码通过命令行参数获取用户输入的参数值。通过使用argcargv参数,程序可以接受用户在命令行中提供的输入,并对其进行处理。这种技巧使得程序可以灵活地接受用户定义的输入。
  2. 并行区域创建:通过使用#pragma omp parallel指令,创建了一个并行区域。该指令将下面的代码块标记为并行执行。在这个并行区域中,代码将被多个线程同时执行,从而实现并行计算。
  3. 线程编号和线程总数获取:通过调用omp_get_thread_numomp_get_num_threads函数,获取当前线程的编号和线程的总数。这些信息对于线程间的任务分配和协调非常重要。线程编号可以用于区分不同的线程,线程总数则用于确定任务划分的方式。
  4. 互斥访问保护:通过使用#pragma omp critical指令,对对共享变量total_sum的更新进行互斥访问保护。这样可以确保在任意时刻只有一个线程能够访问或修改共享变量,从而避免了竞争条件和数据不一致的问题。
  5. 执行时间测量:通过使用omp_get_wtime函数,测量程序的执行时间。这个函数返回一个双精度浮点数,表示从计时器启动到调用该函数的时间间隔。这种技巧允许程序在并行环境中测量并报告程序的执行时间。
  6. 并行计算和互斥访问的结合:代码中通过并行计算和互斥访问的结合,实现了多线程下的并行求和。每个线程负责一部分工作,通过互斥访问保证了共享变量的正确更新。这种技巧可以提高并行计算的效率和可靠性。

容易出错的地方

  1. 命令行参数处理:代码假设命令行参数的格式是正确的,即第一个参数是整数n。如果用户提供了不符合要求的参数格式,例如非整数参数或没有提供足够的参数,代码可能会出现错误。
  2. 并行区域创建和互斥访问:在使用OpenMP创建并行区域时,需要确保共享变量的互斥访问得到正确的保护。在这段代码中,使用了#pragma omp critical来保护对共享变量total_sum的更新。但是,如果其他地方也使用了#pragma omp critical来访问相同的共享变量,可能会导致死锁或性能下降。
  3. 线程总数的控制:代码假设并行区域中的线程总数是系统默认的线程总数。然而,实际情况可能与预期不同,因为线程总数可能受到OpenMP环境变量或编译器指令的影响。在编写代码时,要确保线程总数与预期一致,或者显式地控制线程总数。
  4. 局部变量的正确性:在并行区域内定义的局部变量,如local_startlocal_endlocal_sum,需要确保在每个线程中具有正确的值。这些变量的计算和使用应该与线程编号和线程总数相关联,以确保正确的工作范围划分和局部求和计算。
  5. 互斥访问的性能开销:互斥访问会导致线程之间的竞争和等待,从而引入一定的性能开销。在处理共享变量时,要考虑到互斥访问可能对程序性能的影响,并在必要时寻找更适合的并行算法或数据共享策略。
  6. 线程间数据依赖:在并行计算中,如果存在线程间的数据依赖关系,可能会导致结果错误或性能下降。在编写并行代码时,需要注意线程间的数据依赖关系,并确保正确的同步或数据共享机制。

心得体会

使用OpenMP时,确保要正确包含头文件#include <omp.h>,并在编译时添加-fopenmp选项。

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

当使用critical指令时,务必仔细确认需要保护的代码范围。过大的临界区可能导致性能下降,过小的临界区可能导致数据竞争和不正确的结果。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值