📘
CMake Cookbook
  • Introduction
  • 前言
  • 第0章 配置环境
    • 0.1 获取代码
    • 0.2 Docker镜像
    • 0.3 安装必要的软件
    • 0.4 测试环境
    • 0.5 上报问题并提出改进建议
  • 第1章 从可执行文件到库
    • 1.1 将单个源文件编译为可执行文件
    • 1.2 切换生成器
    • 1.3 构建和链接静态库和动态库
    • 1.4 用条件句控制编译
    • 1.5 向用户显示选项
    • 1.6 指定编译器
    • 1.7 切换构建类型
    • 1.8 设置编译器选项
    • 1.9 为语言设定标准
    • 1.10 使用控制流
  • 第2章 检测环境
    • 2.1 检测操作系统
    • 2.2 处理与平台相关的源代码
    • 2.3 处理与编译器相关的源代码
    • 2.4 检测处理器体系结构
    • 2.5 检测处理器指令集
    • 2.6 为Eigen库使能向量化
  • 第3章 检测外部库和程序
    • 3.1 检测Python解释器
    • 3.2 检测Python库
    • 3.3 检测Python模块和包
    • 3.4 检测BLAS和LAPACK数学库
    • 3.5 检测OpenMP的并行环境
    • 3.6 检测MPI的并行环境
    • 3.7 检测Eigen库
    • 3.8 检测Boost库
    • 3.9 检测外部库:Ⅰ. 使用pkg-config
    • 3.10 检测外部库:Ⅱ. 自定义find模块
  • 第4章 创建和运行测试
    • 4.1 创建一个简单的单元测试
    • 4.2 使用Catch2库进行单元测试
    • 4.3 使用Google Test库进行单元测试
    • 4.4 使用Boost Test进行单元测试
    • 4.5 使用动态分析来检测内存缺陷
    • 4.6 预期测试失败
    • 4.7 使用超时测试运行时间过长的测试
    • 4.8 并行测试
    • 4.9 运行测试子集
    • 4.10 使用测试固件
  • 第5章 配置时和构建时的操作
    • 5.1 使用平台无关的文件操作
    • 5.2 配置时运行自定义命令
    • 5.3 构建时运行自定义命令:Ⅰ. 使用add_custom_command
    • 5.4 构建时运行自定义命令:Ⅱ. 使用add_custom_target
    • 5.5 构建时为特定目标运行自定义命令
    • 5.6 探究编译和链接命令
    • 5.7 探究编译器标志命令
    • 5.8 探究可执行命令
    • 5.9 使用生成器表达式微调配置和编译
  • 第6章 生成源码
    • 6.1 配置时生成源码
    • 6.2 使用Python在配置时生成源码
    • 6.3 构建时使用Python生成源码
    • 6.4 记录项目版本信息以便报告
    • 6.5 从文件中记录项目版本
    • 6.6 配置时记录Git Hash值
    • 6.7 构建时记录Git Hash值
  • 第7章 构建项目
    • 7.1 使用函数和宏重用代码
    • 7.2 将CMake源代码分成模块
    • 7.3 编写函数来测试和设置编译器标志
    • 7.4 用指定参数定义函数或宏
    • 7.5 重新定义函数和宏
    • 7.6 使用废弃函数、宏和变量
    • 7.7 add_subdirectory的限定范围
    • 7.8 使用target_sources避免全局变量
    • 7.9 组织Fortran项目
  • 第8章 超级构建模式
    • 8.1 使用超级构建模式
    • 8.2 使用超级构建管理依赖项:Ⅰ.Boost库
    • 8.3 使用超级构建管理依赖项:Ⅱ.FFTW库
    • 8.4 使用超级构建管理依赖项:Ⅲ.Google Test框架
    • 8.5 使用超级构建支持项目
  • 第9章 语言混合项目
    • 9.1 使用C/C++库构建Fortran项目
    • 9.2 使用Fortran库构建C/C++项目
    • 9.3 使用Cython构建C++和Python项目
    • 9.4 使用Boost.Python构建C++和Python项目
    • 9.5 使用pybind11构建C++和Python项目
    • 9.6 使用Python CFFI混合C,C++,Fortran和Python
  • 第10章 编写安装程序
    • 10.1 安装项目
    • 10.2 生成输出头文件
    • 10.3 输出目标
    • 10.4 安装超级构建
  • 第11章 打包项目
    • 11.1 生成源代码和二进制包
    • 11.2 通过PyPI发布使用CMake/pybind11构建的C++/Python项目
    • 11.3 通过PyPI发布使用CMake/CFFI构建C/Fortran/Python项目
    • 11.4 以Conda包的形式发布一个简单的项目
    • 11.5 将Conda包作为依赖项发布给项目
  • 第12章 构建文档
    • 12.1 使用Doxygen构建文档
    • 12.2 使用Sphinx构建文档
    • 12.3 结合Doxygen和Sphinx
  • 第13章 选择生成器和交叉编译
    • 13.1 使用CMake构建Visual Studio 2017项目
    • 13.2 交叉编译hello world示例
    • 13.3 使用OpenMP并行化交叉编译Windows二进制文件
  • 第14章 测试面板
    • 14.1 将测试部署到CDash
    • 14.2 CDash显示测试覆盖率
    • 14.3 使用AddressSanifier向CDash报告内存缺陷
    • 14.4 使用ThreadSaniiser向CDash报告数据争用
  • 第15章 使用CMake构建已有项目
    • 15.1 如何开始迁移项目
    • 15.2 生成文件并编写平台检查
    • 15.3 检测所需的链接和依赖关系
    • 15.4 复制编译标志
    • 15.5 移植测试
    • 15.6 移植安装目标
    • 15.7 进一步迁移的措施
    • 15.8 项目转换为CMake的常见问题
  • 第16章 可能感兴趣的书
    • 16.1 留下评论——让其他读者知道你的想法
Powered by GitBook
On this page
  • 准备工作
  • 具体实施
  • 工作原理
  • 更多信息

Was this helpful?

  1. 第3章 检测外部库和程序

3.7 检测Eigen库

Previous3.6 检测MPI的并行环境Next3.8 检测Boost库

Last updated 5 years ago

Was this helpful?

NOTE:此示例代码可以在 中找到,包含一个C++的示例。该示例在CMake 3.9版(或更高版本)中是有效的,并且已经在GNU/Linux、macOS和Windows上进行过测试。 中也有一个适用于CMake 3.5的C++示例。

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

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

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

本示例中会实现,矩阵-向量乘法和[LU分解](),可以选择卸载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::Eigen和OpenMP::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。

虽然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 ..

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

https://github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-03/recipe-07
https://github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-03/recipe-06
https://zh.wikipedia.org/wiki/LU%E5%88%86%E8%A7%A3
https://cmake.org/cmake/help/v3.5/command/find_package.html