# 3.5 检测OpenMP的并行环境

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

目前，市面上的计算机几乎都是多核机器，对于性能敏感的程序，我们必须关注这些多核处理器，并在编程模型中使用并发。OpenMP是多核处理器上并行性的标准之一。为了从OpenMP并行化中获得性能收益，通常不需要修改或重写现有程序。一旦确定了代码中的性能关键部分，例如：使用分析工具，程序员就可以通过预处理器指令，指示编译器为这些区域生成可并行的代码。

本示例中，我们将展示如何编译一个包含OpenMP指令的程序(前提是使用一个支持OpenMP的编译器)。有许多支持OpenMP的Fortran、C和C++编译器。对于相对较新的CMake版本，为OpenMP提供了非常好的支持。本示例将展示如何在使用CMake 3.9或更高版本时，使用简单C++和Fortran程序来链接到OpenMP。

**NOTE**:*根据Linux发行版的不同，Clang编译器的默认版本可能不支持OpenMP。使用或非苹果版本的Clang(例如，Conda提供的)或GNU编译器,除非单独安装libomp库(*<https://iscinumpy.gitlab.io/post/omp-on-high-sierra/> *)，否则本节示例将无法在macOS上工作。*

## 准备工作

C和C++程序可以通过包含`omp.h`头文件和链接到正确的库，来使用OpenMP功能。编译器将在性能关键部分之前添加预处理指令，并生成并行代码。在本示例中，我们将构建以下示例源代码(`example.cpp`)。这段代码从1到N求和，其中N作为命令行参数:

```cpp
#include <iostream>
#include <omp.h>
#include <string>

int main(int argc, char *argv[])
{
  std::cout << "number of available processors: " << omp_get_num_procs()
            << std::endl;
  std::cout << "number of threads: " << omp_get_max_threads() << std::endl;
  auto n = std::stol(argv[1]);
  std::cout << "we will form sum of numbers from 1 to " << n << std::endl;
  // start timer
  auto t0 = omp_get_wtime();
  auto s = 0LL;
#pragma omp parallel for reduction(+ : s)
  for (auto i = 1; i <= n; i++)
  {
    s += i;
  }
  // stop timer
  auto t1 = omp_get_wtime();

  std::cout << "sum: " << s << std::endl;
  std::cout << "elapsed wall clock time: " << t1 - t0 << " seconds" << std::endl;

  return 0;
}
```

在Fortran语言中，需要使用`omp_lib`模块并链接到库。在性能关键部分之前的代码注释中，可以再次使用并行指令。例如：`F90`需要包含以下内容:

```
program example

  use omp_lib

  implicit none

  integer(8) :: i, n, s
  character(len=32) :: arg
  real(8) :: t0, t1

  print *, "number of available processors:", omp_get_num_procs()
  print *, "number of threads:", omp_get_max_threads()

  call get_command_argument(1, arg)
  read(arg , *) n

  print *, "we will form sum of numbers from 1 to", n

  ! start timer
  t0 = omp_get_wtime()

  s = 0
!$omp parallel do reduction(+:s)
  do i = 1, n
  s = s + i
  end do

  ! stop timer
  t1 = omp_get_wtime()

  print *, "sum:", s
  print *, "elapsed wall clock time (seconds):", t1 - t0

end program
```

## 具体实施

对于C++和Fortran的例子，`CMakeLists.txt`将遵循一个模板，该模板在这两种语言上很相似：

1. 两者都定义了CMake最低版本、项目名称和语言(CXX或Fortran；我们将展示C++版本):

   ```
   cmake_minimum_required(VERSION 3.9 FATAL_ERROR)
   project(recipe-05 LANGUAGES CXX)
   ```
2. 使用C++11标准:

   ```
   set(CMAKE_CXX_STANDARD 11)
   set(CMAKE_CXX_EXTENSIONS OFF)
   set(CMAKE_CXX_STANDARD_REQUIRED ON)
   ```
3. 调用find\_package来搜索OpenMP:

   ```
   find_package(OpenMP REQUIRED)
   ```
4. 最后，我们定义可执行目标，并链接到FindOpenMP模块提供的导入目标(在Fortran的情况下，我们链接到`OpenMP::OpenMP_Fortran`):

   ```
   add_executable(example example.cpp)
   target_link_libraries(example
     PUBLIC
         OpenMP::OpenMP_CXX
     )
   ```
5. 现在，可以配置和构建代码了:

   ```
   $ mkdir -p build
   $ cd build
   $ cmake ..
   $ cmake --build .
   ```
6. 并行测试(在本例中使用了4个内核):

   ```
   $ ./example 1000000000

   number of available processors: 4
   number of threads: 4
   we will form sum of numbers from 1 to 1000000000
   sum: 500000000500000000
   elapsed wall clock time: 1.08343 seconds
   ```
7. 为了比较，我们可以重新运行这个例子，并将OpenMP线程的数量设置为1:

   ```
   $ env OMP_NUM_THREADS=1 ./example 1000000000

   number of available processors: 4
   number of threads: 1
   we will form sum of numbers from 1 to 1000000000
   sum: 500000000500000000
   elapsed wall clock time: 2.96427 seconds
   ```

## 工作原理

我们的示例很简单：编译代码，并运行在多个内核上时，我们会看到加速效果。加速效果并不是`OMP_NUM_THREADS`的倍数，不过本示例中并不关心，因为我们更关注的是如何使用CMake配置需要使用OpenMP的项目。我们发现链接到OpenMP非常简单，这要感谢`FindOpenMP`模块:

```
target_link_libraries(example
    PUBLIC
        OpenMP::OpenMP_CXX
    )
```

我们不关心编译标志或包含目录——这些设置和依赖项是在`OpenMP::OpenMP_CXX`中定义的(`IMPORTED`类型)。如第1章第3节中提到的，`IMPORTED`库是伪目标，它完全是我们自己项目的外部依赖项。要使用OpenMP，需要设置一些编译器标志，包括目录和链接库。所有这些都包含在`OpenMP::OpenMP_CXX`的属性上，并通过使用`target_link_libraries`命令传递给`example`。这使得在CMake中，使用库变得非常容易。我们可以使用`cmake_print_properties`命令打印接口的属性，该命令由`CMakePrintHelpers.CMake`模块提供:

```
include(CMakePrintHelpers)
cmake_print_properties(
    TARGETS
        OpenMP::OpenMP_CXX
    PROPERTIES
        INTERFACE_COMPILE_OPTIONS
        INTERFACE_INCLUDE_DIRECTORIES
        INTERFACE_LINK_LIBRARIES
    )
```

所有属性都有`INTERFACE_`前缀，因为这些属性对所需目标，需要以接口形式提供，并且目标以接口的方式使用OpenMP。

对于低于3.9的CMake版本:

```
add_executable(example example.cpp)

target_compile_options(example
  PUBLIC
      ${OpenMP_CXX_FLAGS}
  )

set_target_properties(example
  PROPERTIES
      LINK_FLAGS ${OpenMP_CXX_FLAGS}
  )
```

对于低于3.5的CMake版本，我们需要为Fortran项目显式定义编译标志。

在这个示例中，我们讨论了C++和Fortran。相同的参数和方法对于C项目也有效。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://chenxiaowei.gitbook.io/cmake-cookbook/3.0-chinese/3.5-chinese.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
