> For the complete documentation index, see [llms.txt](https://chenxiaowei.gitbook.io/cmake-cookbook/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://chenxiaowei.gitbook.io/cmake-cookbook/3.0-chinese/3.6-chinese.md).

# 3.6 检测MPI的并行环境

**NOTE**:*此示例代码可以在* <https://github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-03/recipe-06> *中找到，包含一个C++和一个C的示例。该示例在CMake 3.9版(或更高版本)中是有效的，并且已经在GNU/Linux、macOS和Windows上进行过测试。*<https://github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-03/recipe-06> *中也有一个适用于CMake 3.5的C示例。*

消息传递接口(Message Passing Interface, MPI)，可以作为OpenMP(共享内存并行方式)的补充，它也是分布式系统上并行程序的实际标准。尽管，最新的MPI实现也允许共享内存并行，但高性能计算中的一种典型方法就是，在计算节点上OpenMP与MPI结合使用。MPI标准的实施包括:

1. 运行时库
2. 头文件和Fortran 90模块
3. 编译器的包装器，用来调用编译器，使用额外的参数来构建MPI库，以处理目录和库。通常，包装器`mpic++/mpiCC/mpicxx`用于C++，`mpicc`用于C，`mpifort`用于Fortran。
4. 启动MPI：应该启动程序，以编译代码的并行执行。它的名称依赖于实现，可以使用这几个命令启动：`mpirun`、`mpiexec`或`orterun`。

本示例，将展示如何在系统上找到合适的MPI实现，从而编译一个简单的“Hello, World”MPI例程。

## 准备工作

示例代码(`hello-mpi.cpp`，可从<http://www.mpitutorial.com> 下载)将在本示例中进行编译，它将初始化MPI库，让每个进程打印其名称:

```cpp
#include <iostream>

#include <mpi.h>

int main(int argc, char **argv)
{
  // Initialize the MPI environment. The two arguments to MPI Init are not
  // currently used by MPI implementations, but are there in case future
  // implementations might need the arguments.
  MPI_Init(NULL, NULL);

  // Get the number of processes
  int world_size;
  MPI_Comm_size(MPI_COMM_WORLD, &world_size);

  // Get the rank of the process
  int world_rank;
  MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);

  // Get the name of the processor
  char processor_name[MPI_MAX_PROCESSOR_NAME];
  int name_len;
  MPI_Get_processor_name(processor_name, &name_len);

  // Print off a hello world message
  std::cout << "Hello world from processor " << processor_name << ", rank "
            << world_rank << " out of " << world_size << " processors" << std::endl;

  // Finalize the MPI environment. No more MPI calls can be made after this
  MPI_Finalize();
}
```

## 具体实施

这个示例中，我们先查找MPI实现：库、头文件、编译器包装器和启动器。为此，我们将用到`FindMPI.cmake`标准CMake模块:

1. 首先，定义了CMake最低版本、项目名称、支持的语言和语言标准:

   ```
   cmake_minimum_required(VERSION 3.9 FATAL_ERROR)

   project(recipe-06 LANGUAGES CXX)

   set(CMAKE_CXX_STANDARD 11)
   set(CMAKE_CXX_EXTENSIONS OFF)
   set(CMAKE_CXX_STANDARD_REQUIRED ON)
   ```
2. 然后，调用`find_package`来定位MPI:

   ```
   find_package(MPI REQUIRED)
   ```
3. 与前面的配置类似，定义了可执行文件的的名称和相关源码，并链接到目标:

   ```
   add_executable(hello-mpi hello-mpi.cpp)

   target_link_libraries(hello-mpi
     PUBLIC
          MPI::MPI_CXX
     )
   ```
4. 配置和构建可执行文件:

   ```
   $ mkdir -p build
   $ cd build
   $ cmake .. # -D CMAKE_CXX_COMPILER=mpicxx C++例子中可加，加与不加对于构建结果没有影响╭(╯^╰)╮

   -- ...
   -- Found MPI_CXX: /usr/lib/openmpi/libmpi_cxx.so (found version "3.1")
   -- Found MPI: TRUE (found version "3.1")
   -- ...

   $ cmake --build .
   ```
5. 为了并行执行这个程序，我们使用`mpirun`启动器(本例中，启动了两个任务):

   ```
   $ mpirun -np 2 ./hello-mpi

   Hello world from processor larry, rank 1 out of 2 processors
   Hello world from processor larry, rank 0 out of 2 processors
   ```

## 工作原理

请记住，编译包装器是对MPI库编译器的封装。底层实现中，将会调用相同的编译器，并使用额外的参数(如成功构建并行程序所需的头文件包含路径和库)来扩充它。

编译和链接源文件时，包装器用了哪些标志？我们可以使用`--showme`选项来查看。要找出编译器的标志，我们可以这样使用:

```
$ mpicxx --showme:compile

-pthread
```

为了找出链接器标志，我们可以这样:

```
$ mpicxx --showme:link

-pthread -Wl,-rpath -Wl,/usr/lib/openmpi -Wl,--enable-new-dtags -L/usr/lib/openmpi -lmpi_cxx -lmpi
```

与之前的OpenMP配置类似，我们发现到MPI的链接非常简单，这要归功于`FindMPI`模块提供的目标:

正如在前面的配方中所讨论的，对于CMake版本低于3.9，需要更多的工作量:

```
add_executable(hello-mpi hello-mpi.c)

target_compile_options(hello-mpi
  PUBLIC
      ${MPI_CXX_COMPILE_FLAGS}
  )

target_include_directories(hello-mpi
  PUBLIC
      ${MPI_CXX_INCLUDE_PATH}
  )

target_link_libraries(hello-mpi
  PUBLIC
      ${MPI_CXX_LIBRARIES}
  )
```

本示例中，我们讨论了C++项目。其中的参数和方法对于C或Fortran项目同样有效。
