MPI调试技巧——基于Clion和GDB

问题背景

最近在学习MPI(由微软维护的那个msmpi,其他还有OpenMPI、MPICH等,感觉大同小异),跑了一段《并行计算与高性能计算》这本书上会导致进程死锁的代码,然后突发奇想说能不能用传统的断点调试方法看一看中间变量以及定位问题出现的位置,结果打了断点再点击调试运行,程序直接略过了断点,并且断点显示为“不可用”。(见下图)

在这里插入图片描述

当时实在是百思不得其解,搜索了一圈关键词,并且也在提问区发表了问题,还是没有找到合适的解决办法,一度以为MPI调试只能依靠打日志,但C++似乎没有相关的库可以用,而且打日志也面临着几个问题:

  • 在MPI中,直接使用cout或者printf无法实时查看输出,或许可以选择用flush函数实时刷新提示信息到控制台,但如果日志体量庞大,如何快速定位问题出现的位置也成了一大麻烦事(打印一大堆中间变量无疑会让问题变得复杂),而且控制台的输出很难再现,尤其面对一些随机出现的bug束手无策;
  • 将日志写入文件或许是一个不错的选择,但在并发环境中维护日志可能涉及IO的高频使用,甚至进程可能在竞争IO的时候发生死锁;
  • 网上也有高手分享自己编写的基于并发环境的日志系统,如果有需要肯定得自己串一遍日志的写法,当然这需要较长的时间。

gdb调试的一个明显的优越性就是查看或修改中间变量很方便,如果能结合控制台输出大致定位bug的位置,再在对应位置敲上断点,调试时就会方便得多。


解决方案:使用Clion的“附加到进程“

如果调试器为gdb,那么这个功能其实就是将gdb调试器挂载到一个正在运行的进程上,这里有一个小技巧,就是为了保证调试能同步执行,需要在断点前后加上MPI_Barrier函数使各进程同步,随后调用sleep函数(头文件为unistd.h)让所有进程暂停运行,就像这样:

在这里插入图片描述

暂停的时间可以尽量设长一些,这样才有充足的时间等待gdb程序命中所有进程的断点,接下来考验手速的时候到了——

  • 以“调试”运行当前代码:

在这里插入图片描述

  • 开始运行后赶紧点击菜单栏的”运行”->“附加到进程”:

在这里插入图片描述

  • 在弹出列表中选择需要调试的进程(我这里创建了6个进程,可以逐个附加,也可以只调试主进程):

在这里插入图片描述

  • 到这里,如果之前设置的sleep时间比较长,断点可能还未命中,可以耐心等待片刻,然后就能看到常规的debug界面了:

在这里插入图片描述

目前来看,这个方法虽然比较麻烦,但是网上似乎也没有更好的办法可以解决多进程调试的问题,而且我个人真的喜欢clion的debug界面(不然也不会花时间琢磨如何在Clion中调试多进程了),后续如果发现什么更好的办法就会在这里更新,也期待看到这里的朋友给出相应的反馈意见。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
MPI和OpenMP都是并行程序设计的重要工具,可以实现高效的并行计算。下面介绍两种基于MPI+OpenMP的并行排序算法:归并排序和快速排序。 ## 归并排序 归并排序是一种分治算法,它将待排序的数组分成两个子数组,分别排序,然后将已排序的子数组合并成一个更大的有序数组。该算法的时间复杂度为O(nlogn)。 ### 并行实现 1. 每个进程读取并分配一部分待排序数据。 2. 每个进程使用OpenMP并行进行归并排序。 3. 将每个进程排序后的子数组发送到Master进程。 4. Master进程使用归并操作合并每个子数组,得到最终的有序数组。 代码实现如下: ```c++ #include <stdio.h> #include <stdlib.h> #include <mpi.h> #include <omp.h> void merge(int *arr, int left, int mid, int right) { int i, j, k; int n1 = mid - left + 1; int n2 = right - mid; int L[n1], R[n2]; for (i = 0; i < n1; i++) L[i] = arr[left + i]; for (j = 0; j < n2; j++) R[j] = arr[mid + 1 + j]; i = 0; j = 0; k = left; while (i < n1 && j < n2) { if (L[i] <= R[j]) { arr[k] = L[i]; i++; } else { arr[k] = R[j]; j++; } k++; } while (i < n1) { arr[k] = L[i]; i++; k++; } while (j < n2) { arr[k] = R[j]; j++; k++; } } void mergeSort(int *arr, int left, int right) { if (left < right) { int mid = left + (right - left) / 2; #pragma omp parallel sections { #pragma omp section { mergeSort(arr, left, mid); } #pragma omp section { mergeSort(arr, mid + 1, right); } } merge(arr, left, mid, right); } } int main(int argc, char **argv) { int *data = NULL; int *sub_data = NULL; int *sub_sorted_data = NULL; int n = 1000000; int i, j, k, p, rank, size; double start, end; MPI_Init(&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD, &rank); MPI_Comm_size(MPI_COMM_WORLD, &size); sub_data = (int *)malloc(n / size * sizeof(int)); sub_sorted_data = (int *)malloc(n / size * sizeof(int)); if (rank == 0) { data = (int *)malloc(n * sizeof(int)); for (i = 0; i < n; i++) { data[i] = rand() % n; } } start = MPI_Wtime(); MPI_Scatter(data, n / size, MPI_INT, sub_data, n / size, MPI_INT, 0, MPI_COMM_WORLD); mergeSort(sub_data, 0, n / size - 1); MPI_Gather(sub_data, n / size, MPI_INT, data, n / size, MPI_INT, 0, MPI_COMM_WORLD); if (rank == 0) { int *temp = (int *)malloc(n * sizeof(int)); for (i = 0; i < size; i++) { for (j = 0; j < n / size; j++) { temp[i * n / size + j] = data[i + j * size]; } } mergeSort(temp, 0, n - 1); free(temp); } end = MPI_Wtime(); if (rank == 0) { printf("Time: %lf seconds\n", end - start); } MPI_Finalize(); free(data); free(sub_data); free(sub_sorted_data); return 0; } ``` ## 快速排序 快速排序是一种分治算法,它通过选择一个基准元素,将数组分成两个子数组,其中一个子数组中的元素都小于基准元素,另一个子数组中的元素都大于基准元素,然后递归地排序子数组。该算法的时间复杂度为O(nlogn)。 ### 并行实现 1. 每个进程读取并分配一部分待排序数据。 2. 每个进程使用OpenMP并行进行快速排序。 3. 将每个进程排序后的子数组发送到Master进程。 4. Master进程使用归并操作合并每个子数组,得到最终的有序数组。 代码实现如下: ```c++ #include <stdio.h> #include <stdlib.h> #include <mpi.h> #include <omp.h> void swap(int *a, int *b) { int temp = *a; *a = *b; *b = temp; } int partition(int *arr, int low, int high) { int pivot = arr[high]; int i = low - 1; int j; #pragma omp parallel for for (j = low; j <= high - 1; j++) { if (arr[j] < pivot) { i++; swap(&arr[i], &arr[j]); } } swap(&arr[i + 1], &arr[high]); return i + 1; } void quickSort(int *arr, int low, int high) { if (low < high) { int pi = partition(arr, low, high); #pragma omp parallel sections { #pragma omp section { quickSort(arr, low, pi - 1); } #pragma omp section { quickSort(arr, pi + 1, high); } } } } int main(int argc, char **argv) { int *data = NULL; int *sub_data = NULL; int *sub_sorted_data = NULL; int n = 1000000; int i, j, k, p, rank, size; double start, end; MPI_Init(&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD, &rank); MPI_Comm_size(MPI_COMM_WORLD, &size); sub_data = (int *)malloc(n / size * sizeof(int)); sub_sorted_data = (int *)malloc(n / size * sizeof(int)); if (rank == 0) { data = (int *)malloc(n * sizeof(int)); for (i = 0; i < n; i++) { data[i] = rand() % n; } } start = MPI_Wtime(); MPI_Scatter(data, n / size, MPI_INT, sub_data, n / size, MPI_INT, 0, MPI_COMM_WORLD); quickSort(sub_data, 0, n / size - 1); MPI_Gather(sub_data, n / size, MPI_INT, data, n / size, MPI_INT, 0, MPI_COMM_WORLD); if (rank == 0) { int *temp = (int *)malloc(n * sizeof(int)); for (i = 0; i < size; i++) { for (j = 0; j < n / size; j++) { temp[i * n / size + j] = data[i + j * size]; } } quickSort(temp, 0, n - 1); free(temp); } end = MPI_Wtime(); if (rank == 0) { printf("Time: %lf seconds\n", end - start); } MPI_Finalize(); free(data); free(sub_data); free(sub_sorted_data); return 0; } ``` 以上两种算法都可以通过MPI+OpenMP实现并行排序,提高了算法的效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我本鼠狂人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值