返回介绍

3.7 检测 Eigen 库

发布于 2025-05-06 21:45:55 字数 7439 浏览 0 评论 0 收藏

NOTE : 此示例代码可以在 https://github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-03/recipe-07 中找到,包含一个 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++示例。

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

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

本示例将展示如何查找 Eigen 库,使用 OpenMP 并行化,并将部分工作转移到 BLAS 库。

本示例中会实现,矩阵-向量乘法和[LU 分解]( https://zh.wikipedia.org/wiki/LU%E5%88%86%E8%A7%A3 ),可以选择卸载 BLAS 和 LAPACK 库中的一些实现。这个示例中,只考虑将在 BLAS 库中卸载。

准备工作

本例中,我们编译一个程序,该程序会从命令行获取的随机方阵和维向量。然后我们将用 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 库中卸载部分线性代数实现:

  1. 首先声明 CMake 最低版本、项目名称和使用 C++11 语言标准:
    cmake_minimum_required(VERSION 3.9 FATAL_ERROR)
    ​
    project(recipe-07 LANGUAGES CXX)
    ​
    set(CMAKE_CXX_STANDARD 11)
    set(CMAKE_CXX_EXTENSIONS OFF)
    set(CMAKE_CXX_STANDARD_REQUIRED ON)
  2. 因为 Eigen 可以使用共享内存的方式,所以可以使用 OpenMP 并行处理计算密集型操作:
    find_package(OpenMP REQUIRED)
  3. 调用 find_package 来搜索 Eigen(将在下一小节中讨论):
    find_package(Eigen3 3.3 REQUIRED CONFIG)
  4. 如果找到 Eigen,我们将打印状态信息。注意,使用的是 Eigen3::Eigen ,这是一个 IMPORT 目标,可通过提供的 CMake 脚本找到这个目标:
    if(TARGET Eigen3::Eigen)
      message(STATUS "Eigen3 v${EIGEN3_VERSION_STRING} found in ${EIGEN3_INCLUDE_DIR}")
    endif()
  5. 接下来,将源文件声明为可执行目标:
    add_executable(linear-algebra linear-algebra.cpp)
  6. 然后,找到 BLAS。注意,现在不需要依赖项:
    find_package(BLAS)
  7. 如果找到 BLAS,我们可为可执行目标,设置相应的宏定义和链接库:
    if(BLAS_FOUND)
      message(STATUS "Eigen will use some subroutines from BLAS.")
      message(STATUS "See: http://eigen.tuxfamily.org/dox-devel/TopicUsingBlasLapack.html")
      target_compile_definitions(linear-algebra
        PRIVATE
            EIGEN_USE_BLAS
        )
      target_link_libraries(linear-algebra
        PUBLIC
            ${BLAS_LIBRARIES}
        )
    else()
        message(STATUS "BLAS not found. Using Eigen own functions")
    endif()
  8. 最后,我们链接到 Eigen3::EigenOpenMP::OpenMP_CXX 目标。这就可以设置所有必要的编译标示和链接标志:
    target_link_libraries(linear-algebra
      PUBLIC
        Eigen3::Eigen
        OpenMP::OpenMP_CXX
      )
  9. 开始配置:
    $ mkdir -p build
    $ cd build
    $ cmake ..
    ​
    -- ...
    -- Found OpenMP_CXX: -fopenmp (found version "4.5")
    -- Found OpenMP: TRUE (found version "4.5")
    -- Eigen3 v3.3.4 found in /usr/include/eigen3
    -- ...
    -- Found BLAS: /usr/lib/libblas.so
    -- Eigen will use some subroutines from BLAS.
    -- See: http://eigen.tuxfamily.org/dox-devel/TopicUsingBlasLapack.html
  10. 最后,编译并测试代码。注意,可执行文件使用四个线程运行:
    $ cmake --build .
    $ ./linear-algebra 1000
    ​
    Number of threads used by Eigen: 4
    matrices allocated and initialized Sun Jun 17 2018 11:04:20 AM
    elapsed time: 0.0492328s
    Scaling done, A and b saved Sun Jun 17 2018 11:04:20 AM
    elapsed time: 0.0492328s
    Linear system solver done Sun Jun 17 2018 11:04:20 AM
    elapsed time: 0.483142s
    relative error is 4.21946e-13

工作原理

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

find_package 可以通过选项传递,届时 CMake 将不会使用 FindEigen3.cmake 模块,而是通过特定的 Eigen3Config.cmakeEigen3ConfigVersion.cmakeEigen3Targets.cmake 提供 Eigen3 安装的标准位置( <installation-prefix>/share/eigen3/cmake )。这种包定位模式称为“Config”模式,比 Find<package>.cmake 方式更加通用。有关“模块”模式和“配置”模式的更多信息,可参考官方文档 https://cmake.org/cmake/help/v3.5/command/find_package.html

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

更多信息

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

  1. 通过将 Eigen3 的安装前缀传递给 CMAKE_PREFIX_PATH :
    $ cmake -D CMAKE_PREFIX_PATH=<installation-prefix> ..
  2. 通过传递配置文件的位置作为 Eigen3_DIR :
    $ cmake -D Eigen3_DIR=<installation-prefix>/share/eigen3/cmake ..

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。