CMake 系列(十四):检测 Eigen 库

BLAS 库为矩阵和向量操作提供了标准化接口,不过这个接口是用 Fortran 语言书写的。虽然已经展示过如何在 C++ 中直接使用这些库,但在现代 C++ 程序中,我们希望有更高级的接口。

纯头文件实现的 Eigen 库使用模板编程来提供接口:矩阵和向量的计算会在编译时进行数据类型检查,以确保兼容所有维度的矩阵;密集和稀疏矩阵的运算也可以通过表达式模板高效地实现,例如矩阵-矩阵乘积、线性系统求解器和特征值问题。从 3.3 版开始,Eigen 可以链接到 BLAS 和 LAPACK 库,把某些操作的实现卸载出去,使库的使用更加灵活,从而获得更多的性能收益。

本示例将展示如何查找 Eigen 库,使用 OpenMP 将其并行化,并把部分工作转移到 BLAS 库。示例会实现矩阵-向量乘法和 LU 分解,可以选择卸载 BLAS 和 LAPACK 库中的一些实现;本示例只考虑卸载到 BLAS 库的情况。

注意:本示例包含一个 C++ 示例,在 CMake 3.9 或更高版本中有效,并已在 GNU/Linux、macOS 和 Windows 上测试通过。

具体步骤

本例中,我们编译一个程序,该程序从命令行获取方阵的维度,生成随机方阵和随机向量,然后用 LU 分解求解线性方程组 Ax=b。以下是源代码(linear-algebra.cpp):

#include <chrono>
#include <cmath>
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <vector>

#include <Eigen/Dense>

int main(int argc, char **argv)
{
  if (argc != 2)
  {
    std::cout << "Usage: ./linear-algebra dim" << std::endl;
    return EXIT_FAILURE;
  }
  std::chrono::time_point<std::chrono::system_clock> start, end;
  std::chrono::duration<double> elapsed_seconds;
  std::time_t end_time;
  std::cout << "Number of threads used by Eigen: " << Eigen::nbThreads()
            << std::endl;

  // Allocate matrices and right-hand side vector
  start = std::chrono::system_clock::now();
  int dim = std::atoi(argv[1]);
  Eigen::MatrixXd A = Eigen::MatrixXd::Random(dim, dim);
  Eigen::VectorXd b = Eigen::VectorXd::Random(dim);
  end = std::chrono::system_clock::now();

  // Report times
  elapsed_seconds = end - start;
  end_time = std::chrono::system_clock::to_time_t(end);
  std::cout << "matrices allocated and initialized "
            << std::put_time(std::localtime(&end_time), "%a %b %d %Y %r\n")
            << "elapsed time: " << elapsed_seconds.count() << "s\n";

  start = std::chrono::system_clock::now();
  // Save matrix and RHS
  Eigen::MatrixXd A1 = A;
  Eigen::VectorXd b1 = b;
  end = std::chrono::system_clock::now();
  end_time = std::chrono::system_clock::to_time_t(end);
  std::cout << "Scaling done, A and b saved "
            << std::put_time(std::localtime(&end_time), "%a %b %d %Y %r\n")
            << "elapsed time: " << elapsed_seconds.count() << "s\n";
  start = std::chrono::system_clock::now();
  Eigen::VectorXd x = A.lu().solve(b);
  end = std::chrono::system_clock::now();

  // Report times
  elapsed_seconds = end - start;
  end_time = std::chrono::system_clock::to_time_t(end);
  double relative_error = (A * x - b).norm() / b.norm();
  std::cout << "Linear system solver done "
            << std::put_time(std::localtime(&end_time), "%a %b %d %Y %r\n")
            << "elapsed time: " << elapsed_seconds.count() << "s\n";
  std::cout << "relative error is " << relative_error << std::endl;

  return 0;
}

矩阵-向量乘法和 LU 分解是在 Eigen 库中实现的,但也可以选择 BLAS 和 LAPACK 库中的实现;在这个示例中,我们只考虑 BLAS 库中的实现。

本示例将用到 Eigen 和 BLAS 库,以及 OpenMP:使用 OpenMP 将 Eigen 并行化,并把部分线性代数实现卸载到 BLAS 库。

工作原理

Eigen 支持通过 CMake 查找,这样配置项目就会变得很容易。从 3.3 版开始,Eigen 提供了 CMake 模块,这些模块将导出相应的目标 Eigen3::Eigen。

可以在调用 find_package 时传递选项,此时 CMake 将不会使用 FindEigen3.cmake 模块,而是使用 Eigen3 在标准安装位置(<installation-prefix>/share/eigen3/cmake)提供的 Eigen3Config.cmake、Eigen3ConfigVersion.cmake 和 Eigen3Targets.cmake。这种包定位模式称为 “Config” 模式,比 Find<package>.cmake 方式更通用。

虽然 Eigen3、BLAS 和 OpenMP 声明为 PUBLIC 依赖项,但 EIGEN_USE_BLAS 编译定义声明为 PRIVATE。可以在单独的库目标中汇集库依赖项,而不是直接链接可执行文件;使用 PUBLIC/PRIVATE 关键字,可以根据库目标的依赖关系调整相应的标志和定义。

CMake 将在预定义的位置层次结构中查找配置模块:首先是 CMAKE_PREFIX_PATH,接下来是 <package>_DIR。因此,如果 Eigen3 安装在非标准位置,可以使用这两个选项告诉 CMake 在哪里查找。

系列导航


   转载规则


《CMake 系列(十四):检测 Eigen 库》 由 吴杭沉 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
CMake 系列(十五):检测 Boost 库 CMake 系列(十五):检测 Boost 库
Boost 是一组 C++ 通用库。这些库提供了许多现代 C++ 项目不可或缺、但 C++ 标准尚未提供的功能,例如元编程、处理可选参数和文件系统操作等组件。其中许多特性后来被 C++11、C++14 和 C++17 标准所采纳,但对于需要
2022-06-21
下一篇 
CMake 系列(十三):检测 Python 库 CMake 系列(十三):检测 Python 库
可以使用 Python 工具来分析和操作程序的输出。然而,还有更强大的方法可以将解释语言(如 Python)与编译语言(如 C 或 C++)组合在一起使用。一种是扩展 Python,通过编译成共享库的 C 或 C++ 模块为 Python
2022-06-07
  目录