CLion和WSL配置MPI运行及调试环境

本文将介绍 Windows 下,使用 CLion 和 WSL 配置 MPI 运行及调试环境的方法。

0. 前提

阅读本文前,请确保:

  • Windows 下已启用 WSL2,并安装了任一 Linux 发行版

1. WSL环境配置

(1) 配置编译环境

bash 复制代码
sudo apt-get update
sudo apt-get install build-essential cmake gdb

(2) 配置MPI

MPI 环境可选择通过包管理器配置,也可自行编译源码配置。简便起见,本文采用包管理器进行配置,选用的 MPI 实现为 MPICH,安装命令为:

bash 复制代码
# Debian、Ubuntu等发行版使用如下命令安装:
sudo apt-get install mpich

如果安装成功,运行命令 mpichversion,将输出:

之后,尝试运行测试程序,将以下代码保存到文件 mpi_hello.c 中:

c 复制代码
/* File:       
 *    mpi_hello.c
 *
 * Purpose:    
 *    A "hello,world" program that uses MPI
 *
 * Compile:    
 *    mpicc -g -Wall -std=C99 -o mpi_hello mpi_hello.c
 * Usage:        
 *    mpiexec -n<number of processes> ./mpi_hello
 *
 * Input:      
 *    None
 * Output:     
 *    A greeting from each process
 *
 * Algorithm:  
 *    Each process sends a message to process 0, which prints 
 *    the messages it has received, as well as its own message.
 *
 * IPP:  Section 3.1 (pp. 84 and ff.)
 */
#include <stdio.h>
#include <string.h>  /* For strlen             */
#include <mpi.h>     /* For MPI functions, etc */ 

const int MAX_STRING = 100;

int main(void) {
   char       greeting[MAX_STRING];  /* String storing message */
   int        comm_sz;               /* Number of processes    */
   int        my_rank;               /* My process rank        */

   /* Start up MPI */
   MPI_Init(NULL, NULL); 

   /* Get the number of processes */
   MPI_Comm_size(MPI_COMM_WORLD, &comm_sz); 

   /* Get my rank among all the processes */
   MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); 

   if (my_rank != 0) { 
      /* Create message */
      sprintf(greeting, "Greetings from process %d of %d!", 
            my_rank, comm_sz);
      /* Send message to process 0 */
      MPI_Send(greeting, strlen(greeting)+1, MPI_CHAR, 0, 0,
            MPI_COMM_WORLD); 
   } else {  
      /* Print my message */
      printf("Greetings from process %d of %d!\n", my_rank, comm_sz);
      for (int q = 1; q < comm_sz; q++) {
         /* Receive message from process q */
         MPI_Recv(greeting, MAX_STRING, MPI_CHAR, q,
            0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
         /* Print message from process q */
         printf("%s\n", greeting);
      } 
   }

   /* Shut down MPI */
   MPI_Finalize(); 

   return 0;
}  /* main */

编译并执行:

bash 复制代码
# 编译
mpicc -g -Wall -std=C99 -o mpi_hello mpi_hello.c

# 运行,4表示进程数
mpiexec -n 4 ./mpi_hello

输出应为:

Greetings from process 0 of 4!
Greetings from process 1 of 4!
Greetings from process 2 of 4!
Greetings from process 3 of 4!

2. CLion运行环境配置

(1) 新建项目

CLion 新建一个项目 mpi,将 main.c 或 main.cpp 内容修改为:

c 复制代码
/* File:       
 *    mpi_hello.c
 *
 * Purpose:    
 *    A "hello,world" program that uses MPI
 *
 * Compile:    
 *    mpicc -g -Wall -std=C99 -o mpi_hello mpi_hello.c
 * Usage:        
 *    mpiexec -n<number of processes> ./mpi_hello
 *
 * Input:      
 *    None
 * Output:     
 *    A greeting from each process
 *
 * Algorithm:  
 *    Each process sends a message to process 0, which prints 
 *    the messages it has received, as well as its own message.
 *
 * IPP:  Section 3.1 (pp. 84 and ff.)
 */
#include <stdio.h>
#include <string.h>  /* For strlen             */
#include <mpi.h>     /* For MPI functions, etc */ 

const int MAX_STRING = 100;

int main(void) {
   char       greeting[MAX_STRING];  /* String storing message */
   int        comm_sz;               /* Number of processes    */
   int        my_rank;               /* My process rank        */

   /* Start up MPI */
   MPI_Init(NULL, NULL); 

   /* Get the number of processes */
   MPI_Comm_size(MPI_COMM_WORLD, &comm_sz); 

   /* Get my rank among all the processes */
   MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); 

   if (my_rank != 0) { 
      /* Create message */
      sprintf(greeting, "Greetings from process %d of %d!", 
            my_rank, comm_sz);
      /* Send message to process 0 */
      MPI_Send(greeting, strlen(greeting)+1, MPI_CHAR, 0, 0,
            MPI_COMM_WORLD); 
   } else {  
      /* Print my message */
      printf("Greetings from process %d of %d!\n", my_rank, comm_sz);
      for (int q = 1; q < comm_sz; q++) {
         /* Receive message from process q */
         MPI_Recv(greeting, MAX_STRING, MPI_CHAR, q,
            0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
         /* Print message from process q */
         printf("%s\n", greeting);
      } 
   }

   /* Shut down MPI */
   MPI_Finalize(); 

   return 0;
}  /* main */

(2) 添加工具链

在设置 => 构建、运行、部署 => 工具链中添加一个工具链,类型选 WSL。

CLion 会自动检测填写,但 C 编译器和 C++ 编译器路径需要手动指定,相应路径使用 which 命令获取:

bash 复制代码
# 获取 C 编译器路径
which mpicc

# 获取 C++ 编译器路径
which mpicxx

(3) 指定项目所用工具链

添加工具链后,必须手动设置项目使用新添加的工具链,在设置 => 构建、运行、部署 => CMake 中进行设置:

(4) 修改项目终端(可选)

在设置 => 工具 => 终端中,将 Shell 路径修改为所用的发行版路径,该路径一般为:

# C:\Users\<你的电脑用户名>\AppData\Local\Microsoft\WindowsApps\<发行版名称>.exe,如:
C:\Users\username\AppData\Local\Microsoft\WindowsApps\ubuntu.exe

(5) 修改CMake配置文件

修改项目根目录下的 CMake 配置文件 CMakeLists.txt:

  • 在 add_executable 所在行之前添加:find_package(MPI REQUIRED)
  • 在 add_executable 所在行之后添加:
    • 如果为 C 项目:target_link_libraries(<your_project_name> PRIVATE MPI::MPI_C)
    • 如果为 C++ 项目:target_link_libraries(<your_project_name> PRIVATE MPI::MPI_CXX)
  • 可能需要修改 cmake_minimum_required 中的版本号,使其不高于 WSL 中安装的 CMake 版本

示例:

cmake 复制代码
cmake_minimum_required(VERSION 3.22)
project(mpi)

set(CMAKE_C_STANDARD 11)

find_package(MPI REQUIRED)
add_executable(mpi main.c)
target_link_libraries(mpi PRIVATE MPI::MPI_C)

之后,重新加载 CMake 配置:

尝试构建项目:

(6) 修改运行配置

为了可以一键运行,需要修改运行配置:

需要将可执行文件修改为 mpiexec 的路径(WSL 内通过 which 命令获取),并将程序实参填写为:-n 4 "$CMakeCurrentProductFile$",其中表示运行时使用 4 个进程,可根据需要修改

之后,点击运行,程序输出如图:

3. CLion调试MPI程序

CLion 调试 MPI 程序需要通过使用 GDB 的附加到进程功能间接实现。

(1) 添加断点

在需要打断点的位置打上断点,并在所有断点位置上方添加如下代码:

c 复制代码
int i = 0;
while (!i)
    sleep(5); // 需要包含unistd.h头文件

之后在 sleep(5) 一行添加断点。

(2) 运行程序

点击运行按钮,运行程序。

(3) GDB附加到进程功能

点击工具 => 附加到进程:

在 SSH/WSL 中搜索当前可执行文件:

选取想要调试的进程(在实例中,因为 0 号进程中未添加断点,故不应选取进程号小的进程138113),点击使用WSL GDB附加,进入调试模式。

为了程序可以向下运行,需要将 i 的值设置成 1。

之后正常调试即可。

4. 后记

  • 使用 CLion 只能通过 GDB 一个进程一个进程地调试,未找到方法调用 TotalView 同时跟踪和调试多个进程
  • 运行配置中的程序实参可以填写为:-n $Prompt$ "$CMakeCurrentProductFile$",实现每次运行前弹窗填写需要运行的进程数

参考资料:

  1. How to work with OpenMPI projects in CLion | CLion Documentation
  2. Clion 可以编译调用 MPI 并行库的项目么? - 知乎