> 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.4-chinese.md).

# 3.4 检测BLAS和LAPACK数学库

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

许多数据算法严重依赖于矩阵和向量运算。例如：矩阵-向量和矩阵-矩阵乘法，求线性方程组的解，特征值和特征向量的计算或奇异值分解。这些操作在代码库中非常普遍，因为操作的数据量比较大，因此高效的实现有绝对的必要。幸运的是，有专家库可用：基本线性代数子程序(BLAS)和线性代数包(LAPACK)，为许多线性代数操作提供了标准API。供应商有不同的实现，但都共享API。虽然，用于数学库底层实现，实际所用的编程语言会随着时间而变化(Fortran、C、Assembly)，但是也都是Fortran调用接口。考虑到调用街扩，本示例中的任务要链接到这些库，并展示如何用不同语言编写的库。

## 准备工作

为了展示数学库的检测和连接，我们编译一个C++程序，将矩阵的维数作为命令行输入，生成一个随机的方阵**A**，一个随机向量**b**，并计算线性系统方程: **Ax = b**。另外，将对向量**b**的进行随机缩放。这里，需要使用的子程序是BLAS中的DSCAL和LAPACK中的DGESV来求线性方程组的解。示例C++代码的清单( `linear-algebra.cpp`)：

```cpp
#include "CxxBLAS.hpp"
#include "CxxLAPACK.hpp"

#include <iostream>
#include <random>
#include <vector>

int main(int argc, char** argv) {
  if (argc != 2) {
    std::cout << "Usage: ./linear-algebra dim" << std::endl;
    return EXIT_FAILURE;
  }

  // Generate a uniform distribution of real number between -1.0 and 1.0
  std::random_device rd;
  std::mt19937 mt(rd());
  std::uniform_real_distribution<double> dist(-1.0, 1.0);

  // Allocate matrices and right-hand side vector
  int dim = std::atoi(argv[1]);
  std::vector<double> A(dim * dim);
  std::vector<double> b(dim);
  std::vector<int> ipiv(dim);
  // Fill matrix and RHS with random numbers between -1.0 and 1.0
  for (int r = 0; r < dim; r++) {
    for (int c = 0; c < dim; c++) {
      A[r + c * dim] = dist(mt);
    }
    b[r] = dist(mt);
  }

  // Scale RHS vector by a random number between -1.0 and 1.0
  C_DSCAL(dim, dist(mt), b.data(), 1);
  std::cout << "C_DSCAL done" << std::endl;

  // Save matrix and RHS
  std::vector<double> A1(A);
  std::vector<double> b1(b);
  int info;
  info = C_DGESV(dim, 1, A.data(), dim, ipiv.data(), b.data(), dim);
  std::cout << "C_DGESV done" << std::endl;
  std::cout << "info is " << info << std::endl;

  double eps = 0.0;
  for (int i = 0; i < dim; ++i) {
    double sum = 0.0;
    for (int j = 0; j < dim; ++j)
      sum += A1[i + j * dim] * b[j];
    eps += std::abs(b1[i] - sum);
  }
  std::cout << "check is " << eps << std::endl;

  return 0;
}
```

使用C++11的随机库来生成-1.0到1.0之间的随机分布。`C_DSCAL`和`C_DGESV`分别是到BLAS和LAPACK库的接口。为了避免名称混淆，将在下面来进一步讨论CMake模块：

文件`CxxBLAS.hpp`用`extern "C"`封装链接BLAS:

```cpp
#pragma once
#include "fc_mangle.h"
#include <cstddef>
#ifdef __cplusplus
extern "C" {
#endif
extern void DSCAL(int *n, double *alpha, double *vec, int *inc);
#ifdef __cplusplus
}
#endif
void C_DSCAL(size_t length, double alpha, double *vec, int inc);
```

对应的实现文件`CxxBLAS.cpp`:

```cpp
#include "CxxBLAS.hpp"

#include <climits>

// see http://www.netlib.no/netlib/blas/dscal.f
void C_DSCAL(size_t length, double alpha, double *vec, int inc) {
  int big_blocks = (int)(length / INT_MAX);
  int small_size = (int)(length % INT_MAX);
  for (int block = 0; block <= big_blocks; block++) {
    double *vec_s = &vec[block * inc * (size_t)INT_MAX];
    signed int length_s = (block == big_blocks) ? small_size : INT_MAX;
    ::DSCAL(&length_s, &alpha, vec_s, &inc);
  }
}
```

`CxxLAPACK.hpp`和`CxxLAPACK.cpp`为LAPACK调用执行相应的转换。

## 具体实施

对应的`CMakeLists.txt`包含以下构建块:

1. 我们定义了CMake最低版本，项目名称和支持的语言:

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

   ```
   set(CMAKE_CXX_STANDARD 11)
   set(CMAKE_CXX_EXTENSIONS OFF)
   set(CMAKE_CXX_STANDARD_REQUIRED ON)
   ```
3. 此外，我们验证Fortran和C/C++编译器是否能协同工作，并生成头文件，这个文件可以处理名称混乱。两个功能都由`FortranCInterface`模块提供:

   ```
   include(FortranCInterface)

   FortranCInterface_VERIFY(CXX)

   FortranCInterface_HEADER(
     fc_mangle.h
     MACRO_NAMESPACE "FC_"
     SYMBOLS DSCAL DGESV
     )
   ```
4. 然后，找到BLAS和LAPACK:

   ```
   find_package(BLAS REQUIRED)
   find_package(LAPACK REQUIRED)
   ```
5. 接下来，添加一个库，其中包含BLAS和LAPACK包装器的源代码，并链接到`LAPACK_LIBRARIES`，其中也包含`BLAS_LIBRARIES`:

   ```
   add_library(math "")

   target_sources(math
     PRIVATE
       CxxBLAS.cpp
       CxxLAPACK.cpp
     )

   target_include_directories(math
     PUBLIC
       ${CMAKE_CURRENT_SOURCE_DIR}
       ${CMAKE_CURRENT_BINARY_DIR}
     )

   target_link_libraries(math
     PUBLIC
         ${LAPACK_LIBRARIES}
     )
   ```
6. 注意，目标的包含目录和链接库声明为`PUBLIC`，因此任何依赖于数学库的附加目标也将在其包含目录中。
7. 最后，我们添加一个可执行目标并链接`math`：

   ```
   add_executable(linear-algebra "")

   target_sources(linear-algebra
     PRIVATE
         linear-algebra.cpp
     )

   target_link_libraries(linear-algebra
     PRIVATE
         math
     )
   ```
8. 配置时，我们可以关注相关的打印输出:

   ```
   $ mkdir -p build
   $ cd build
   $ cmake ..

   ...
   -- Detecting Fortran/C Interface
   -- Detecting Fortran/C Interface - Found GLOBAL and MODULE mangling
   -- Verifying Fortran/C Compiler Compatibility
   -- Verifying Fortran/C Compiler Compatibility - Success
   ...
   -- Found BLAS: /usr/lib/libblas.so
   ...
   -- A library with LAPACK API found.
   ...
   ```
9. 最后，构建并测试可执行文件:

   ```
   $ cmake --build .
   $ ./linear-algebra 1000

   C_DSCAL done
   C_DGESV done
   info is 0
   check is 1.54284e-10
   ```

## 工作原理

`FindBLAS.cmake`和`FindLAPACK.cmake`将在标准位置查找BLAS和LAPACK库。对于前者，该模块有`SGEMM`函数的Fortran实现，一般用于单精度矩阵乘积。对于后者，该模块有`CHEEV`函数的Fortran实现，用于计算复杂厄米矩阵的特征值和特征向量。查找在CMake内部，通过编译一个小程序来完成，该程序调用这些函数，并尝试链接到候选库。如果失败，则表示相应库不存于系统上。

生成机器码时，每个编译器都会处理符号混淆，不幸的是，这种操作并不通用，而与编译器相关。为了解决这个问题，我们使用`FortranCInterface`模块( <https://cmake.org/cmake/help/v3.5/module/FortranCInterface.html> )验证Fortran和C/C++能否混合编译，然后生成一个Fortran-C接口头文件`fc_mangle.h`，这个文件用来解决编译器性的问题。然后，必须将生成的`fc_mann .h`包含在接口头文件`CxxBLAS.hpp`和`CxxLAPACK.hpp`中。为了使用`FortranCInterface`，我们需要在`LANGUAGES`列表中添加C和Fortran支持。当然，也可以定义自己的预处理器定义，但是可移植性会差很多。

我们将在第9章中更详细地讨论Fortran和C的互操作性。

**NOTE**:*目前，BLAS和LAPACK的许多实现已经在Fortran外附带了一层C包装。这些包装器多年来已经标准化，称为CBLAS和LAPACKE。*

## 更多信息

许多算法代码比较依赖于矩阵代数运算，使用BLAS和LAPACK API的高性能实现就非常重要了。供应商为不同的体系结构和并行环境提供不同的库，`FindBLAS.cmake`和`FindLAPACK.cmake`可能的无法定位到当前库。如果发生这种情况，可以通过`-D`选项显式地从CLI对库进行设置。


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## 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.4-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.
