简介:Eigen 3.3.9是一份面向C++开发者的开源线性代数库完整源码包,适用于科学计算、计算机图形学、机器学习及物理模拟等需要高效矩阵运算的场景。该版本包含全部源代码、Doxygen API文档、示例程序与测试用例,支持矩阵乘法、求逆、特征值分解、QR/SVD分解及稀疏矩阵操作,通过模板元编程实现接近BLAS的高性能运算,并采用lazy evaluation减少不必要的数据拷贝。压缩包共1611个文件,以cpp、h、hh源文件为主,辅以dox文档、cmake构建脚本、txt说明及f测试文件,整体体积仅2.91MB,轻量且易于集成到各类C++项目中。资源还收录了bench_unrolling等基准测试、SVD/QR等模块示例以及AdolcForward、ArpackSupport等扩展支持,便于开发者快速上手和深入调优。目前已有551人学习下载,适合希望提升数值计算效率的中高级C++程序员参考使用。
1. 项目概述
第一次听到 "eigen-3.3.9" 这个项目名,可能有朋友会觉得陌生,但搞过科学计算、机器人控制、计算机视觉或者量化交易的朋友,大概率都在项目配置文件里见过它的身影。这是Eigen 线性代数库的 3.3.9 版本,一个用 C++ 模板元编程实现的数学核心库,主要解决的是向量、矩阵运算、线性方程组求解、特征值分解这类高频数学操作。
简单说,Eigen 就是 C++ 世界里做矩阵和线性代数运算的头号选择。你用的很多软件背后其实都有它——比如 ROS 机器人操作系统里坐标变换的底层运算、SLAM 算法里的矩阵分解、深度学习框架早期的部分算子实现、OpenCV 里某些几何变换的底层支撑,甚至 Chrome 浏览器某些图形渲染相关的线程里也用到了它。如果拿日常生活的场景打比方,Eigen 就像工具箱里的那套内六角扳手,你未必每天直接拿在手里用,但拆任何精密仪器几乎都要经过它。
本次项目的输入内容很简单,只有一行标题eigen-3.3.9,加上两条热搜词“eigen 下载”和“R语言 eigen 函数”。所以这篇文章我就围绕三个问题展开:第一,Eigen 3.3.9 这个版本到底好在哪,为什么时至今日还有大量老项目死守这个版本不升;第二,下载、编译、接入项目的全流程怎么走,常见坑在哪里;第三,把热搜词里容易混淆的“R 语言 eigen() 函数”和 C++ Eigen 库拆开讲清楚,帮助刚开始接触的朋友避免概念上的误解。
需要提前说明的是,文中的操作路径和排查建议,一部分来自我实际在 Ubuntu 和 Windows 平台上折腾 Eigen 的笔记,另一部分是业内最常见的通用做法。如果你在 macOS 或者其他编译环境下遇到差异,对照着调整一下就行,思路是通用的。
2. 为什么选 Eigen 3.3.9:头文件库的便利性与版本价值
2.1 一个不需要"安装"的数学库
Eigen 和市面上大多数 C++ 库有一个本质区别:它几乎全是头文件(header-only)。当你下载 eigen-3.3.9 之后解压,看到一大堆 .hpp 文件和一个 CMakeLists.txt,没有 .cpp 源文件需要你去编译成静态库或者动态库。使用的时候只需要把Eigen/这个子目录扔到你的 include 路径里,然后在代码里#include <Eigen/Dense>就能开始写矩阵运算了。
这个特性带来的好处非常实在。第一,没有二进制兼容问题。我编译出来的 Eigen 不需要考虑 ABI 是否匹配,因为最终编译时所有的模板代码都会展开到我的目标文件里,这从根本上消除了 "我链接的库和你编译的库版本不一致" 这类头痛问题。第二,跨平台极其简单。同一个 Eigen 头文件,你在 Windows 上、Linux 上、ARM 开发板上都可以直接用,不用分别去下载对应平台的预编译包。第三,交叉编译友好。我在给树莓派或者 RK3399 这类嵌入式板子交叉编译程序时,只需要把 Eigen 头文件路径加进 sysroot 对应的 include 目录就行,完全绕开了“目标平台上要另外装一个库”的麻烦。
3.3.9 作为 3.3 系列的收尾版本,虽然是 2020 年发布的,但它的迭代非常成熟。3.4 出来后虽然有不少新特性,但很多工业项目为了保证"能跑就不动"的原则,仍然把版本锁在 3.3.9 上。所以你可能看见一些老牌开源项目,它们 README 里依然写着Eigen 3.3.9是推荐版本,这不是因为项目组没跟上时代,而是因为 3.3.9 真的非常稳。
2.2 版本 3.3.9 适合谁用
我个人的建议是:如果你的项目没有特殊要求,直接用 3.3.9 完全可以,尤其是在 Ubuntu 18.04/20.04 这类自带版本管理的老系统上,apt 源里默认的 Eigen 恰好就是这个系列,省去了很多手工编译的功夫。
但如果你的目标平台是 macOS 的最新环境,或者准备在新项目里尝鲜,那么直接拉最新版 3.4.x 会更省事,因为新版对最新标准(C++17/C++20)的适配更完整,部分性能也有优化。也就是说,3.3.9 更适合老项目维护、工控机上已有的环境、以及那些依赖老版 Eigen 的算法库(比如某些老版本 Ceres Solver 就对 Eigen 版本有明确要求)。如果你正在准备面试或者做课程作业,用 3.3.9 也完全够用,面试官通常只考察你用 Eigen 解线性方程组、做矩阵变换、处理最小二乘这些问题,不会去卡你具体用的是 3.3 还是 3.4。
3. 使用前的关键区分:C++ Eigen 库与 R 语言 eigen() 函数
因为搜索热词里出现了"R语言 eigen 函数",这里我必须专门把这两个"eigen"给拆开,这两个家伙虽然名字一模一样,但完全是两个世界的产物,初学阶段经常会有人搞混。
3.1 R 语言里的 eigen() 是个内置函数
如果你是在 R 的终端里输入?eigen查帮助文档,然后看到eigen(x, symmetric, only.values = FALSE, EISPACK = TRUE)这样的用法,那你遇到的是 R 的基础包base里提供的特征值分解函数。它的作用是:给一个方阵(或者以矩阵形式存储的数据),返回该矩阵的特征值和特征向量。典型用法是:
# 构造一个 3x3 对称矩阵 m <- matrix(c(4, 1, 0, 1, 3, 1, 0, 1, 2), nrow = 3, byrow = TRUE) # 特征分解 result <- eigen(m) # 查看特征值 result$values # 查看特征向量 result$vectorsR 里的 eigen() 函数是用来做数据分析、统计建模的,你写的是R 脚本,应用场景是因子分析、主成分分析(PCA)、判断矩阵是否可逆等。比如在 PCA 里,要算协方差矩阵的特征值和特征向量,直接eigen(cov(data))一行搞定。它是 R 语言万丈数据分析生态里面的一小块砖石,和 C++ 库没有任何代码层面的关系。
3.2 C++ Eigen 库是编程依赖
C++ 里的 Eigen,是一个用模板写成的 C++ 库,你在代码里这样用:
#include <iostream> #include <Eigen/Dense> int main() { Eigen::Matrix3d m; m << 4, 1, 0, 1, 3, 1, 0, 1, 2; // 计算特征值分解 Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d> solver(m); if (solver.info() != Eigen::Success) { std::cerr << "Eigen decomposition failed." << std::endl; return -1; } std::cout << "Eigenvalues:\n" << solver.eigenvalues() << std::endl; std::cout << "Eigenvectors:\n" << solver.eigenvectors() << std::endl; return 0; }这里Eigen::SelfAdjointEigenSolver是库提供的特征分解器,Eigen::Matrix3d是库里的矩阵类型。这个库解决的是"我在 C++ 程序里怎么高效地做矩阵运算",应用场景是机器人学中的位姿变换、三维重建中的本质矩阵分解、控制系统里的状态方程求解、数值计算里的线性最小二乘等。它的设计哲学是:编译期就确定矩阵大小和运算表达式,以此获得接近手写循环的极致性能。
3.3 怎么判断自己该看哪个
判断标准特别简单:如果你在写 C++ 代码,遇到的是#include <Eigen/...>、Eigen::Matrix、Eigen::Vector3d这类语法,那你用的是 C++ Eigen 库,跟着本文后面的章节去下载和配置即可。如果你是在 RStudio 或者 R 命令行里,输入的是eigen(matrix_data)这样的函数,那你看的是 R 的文档,用不着去下载 C++ 库。两条技术路线唯一共同点就是名字都来自德语"eigen",本意是"自身的、特征性的",所以都用来指代特征值和特征向量相关运算——仅此而已。
4. eigen-3.3.9 的下载与编译配置全流程
这一节我们实际动手。我在 Ubuntu 20.04 环境下的操作记录如下,Windows 用户记得把命令换成对应的 GUI 操作或者 PowerShell 命令即可。
4.1 下载 eigen-3.3.9 的几种方式
第一种方式,直接从官方网站下载压缩包。Eigen 的官网是eigen.tuxfamily.org,入口页里有个下载区(Get it),点进去找3.3.9这个版本,下载一个eigen-3.3.9.tar.gz或者.zip的压缩包。官网下载页面的好处是版本和文件完整,坏处是国外服务器,偶尔会慢一些。如果你在墙内网络下载失败,别慌,可以换国内镜像,比如很多高校开源镜像站也会同步这个包,搜"清华镜像 eigen"类似的入口即可,安全可靠。
第二种方式,用 Git 拉取官方仓库后切到对应 tag:
git clone https://gitlab.com/libeigen/eigen.git cd eigen git checkout 3.3.9这种方式适合你想要 3.3.9 仓库完整历史、后续准备看源码或者给 Eigen 自己提补丁的情况。不过如果你只是要一个能用的库,直接下压缩包更快。
第三种方式,用系统包管理器安装。在 Ubuntu/Debian 上:
sudo apt install libeigen3-dev装好之后默认头文件在/usr/include/eigen3/下。需要注意,不同系统源中这个包对应的 Eigen 版本可能不一样。在 Ubuntu 20.04 上通常装到的是 3.3.7 或 3.3.8,而 3.3.9 并不一定恰好是系统默认版本。如果你有强迫症一定要 3.3.9,建议手动下载源码包,自己放到指定路径。
4.2 让 Eigen 跑起来的两种姿势
第一种姿势:直接把头文件路径告诉编译器,适合小项目、临时验证和教学场景。
假设我解压得到目录/home/user/workspace/libs/eigen-3.3.9,目录下面能看到Eigen/这个子文件夹。那编译上面的测试代码时,我只需要:
g++ -I /home/user/workspace/libs/eigen-3.3.9 test_eigen.cpp -o test_eigen这里-I参数指定的是包含了Eigen/目录的父路径。跑出来的test_eigen没有任何动态库依赖,因为 Eigen 的头文件已经把该展开的模板代码都展开了,最终生成的可执行文件是独立的。
第二种姿势:用 CMake 管理,这是实际工程中最常见的接入方式。在项目的CMakeLists.txt里写:
cmake_minimum_required(VERSION 3.10) project(EigenDemo) set(CMAKE_CXX_STANDARD 11) # 指定 Eigen 头文件路径 include_directories(/home/user/workspace/libs/eigen-3.3.9) add_executable(demo src/main.cpp)如果是通过 apt 装的 Eigen,则在 CMake 里用官方提供的FindEigen3.cmake或者Eigen3Config.cmake更优雅:
find_package(Eigen3 3.3 REQUIRED) include_directories(${EIGEN3_INCLUDE_DIR})这里EIGEN3_INCLUDE_DIR一般会带出类似/usr/include/eigen3的路径。
4.3 验证是否装好了
代码写好了,我们跑一个最小的验证程序:构造一个矩阵,求它的逆和行列式。
#include <iostream> #include <Eigen/Dense> int main() { Eigen::Matrix2d mat; mat << 2, 1, 1, 3; std::cout << "Matrix:\n" << mat << std::endl; std::cout << "Inverse:\n" << mat.inverse() << std::endl; std::cout << "Determinant: " << mat.determinant() << std::endl; Eigen::MatrixXd A(3, 3); A << 4, 1, 0, 1, 3, 1, 0, 1, 2; std::cout << "Eigenvalues of A:\n" << A.eigenvalues() << std::endl; return 0; }如果编译运行后能输出正确的矩阵求逆结果和特征值,那说明 Eigen 已经成功接入了你的项目。注意一个细节:对于动态大小的矩阵MatrixXd,eigenvalues()返回的是复数向量,因为实矩阵可能具有复数特征值;而SelfAdjointEigenSolver只适用于对称/自伴矩阵,它返回的特征值一定是实数值。这两个 API 别混用,否则容易在代码审查里被同事追问。
5. 核心实操:用 Eigen 3.3.9 解决常见的矩阵运算场景
Eigen 能做的事情太多,这里挑四个最常用的高频场景展开:矩阵基本运算、线性方程组求解、特征值分解、最小二乘拟合。这四个场景覆盖了从课程作业到工业落地的大部分需求。
5.1 矩阵基本运算与系数访问
Eigen 的矩阵类型定义非常直观。固定大小的矩阵如Matrix3d代表 3x3 的 double 矩阵,动态大小的矩阵用MatrixXd。向量也有两种,列向量Vector3d和行向量RowVector3d。基本运算直接重载了+、-、*、/操作符,写起来和 MATLAB 很像:
Eigen::Matrix3d A, B; A << 1, 2, 3, 4, 5, 6, 7, 8, 9; B = A * A.transpose(); // 矩阵乘以转置 Eigen::Vector3d v(1, 2, 3); double dot = v.dot(v); // 点积 double norm = v.norm(); // 模长访问单个系数用括号A(i, j),和 MATLAB 的A(i, j)下标一样,但注意索引从 0 开始,这和 R/Matlab 的 1 起始完全不同。刚开始从 R 或者 MATLAB 转过来的同事,最容易在这上面踩坑,我在 code review 里抓到过好几次索引越界问题。
5.2 解线性方程组:PartialPivLU与ColPivHouseholderQR的选择
解Ax = b是工程里最常见的需求。Eigen 提供了多种求解器,速度和精度侧重各有不同。新手最容易犯的错误是直接用A.inverse() * b来求解,实际上这是性能较差的写法,应该优先使用分解器:
Eigen::Matrix3d A; Eigen::Vector3d b, x; A << 2, 1, 0, 1, 3, 1, 0, 1, 2; b << 1, 2, 3; // 方式一:LU 分解 Eigen::PartialPivLU<Eigen::Matrix3d> lu(A); x = lu.solve(b); // 方式二:QR 分解(数值稳定性更好) Eigen::ColPivHouseholderQR<Eigen::Matrix3d> qr(A); x = qr.solve(b);选哪个?如果矩阵是方阵且非奇异,PartialPivLU速度最快;如果矩阵可能接近退化(ill-conditioned),ColPivHouseholderQR更稳;如果矩阵是稀疏的,就要用SparseLU或者BiCGSTAB这类迭代法,这已经是进阶话题了。总体原则是:优先考虑数值稳定性,再追求计算速度,不要为了省几毫秒把结果搞错。
5.3 特征值分解与谱分解
特征值分解在 PCA、谱聚类、振动分析等场景中出现频率很高。Eigen 对不同矩阵类型提供了不同的分解器:
EigenSolver:通用稠密方阵,结果可能是复数。SelfAdjointEigenSolver:对称/自伴矩阵,结果全为实数,速度较快,实际使用最频繁。GeneralizedSelfAdjointEigenSolver:广义特征值问题Ax = λBx。
对 PCA 来说,我一般直接先算协方差矩阵的 SelfAdjointEigenSolver,然后按特征值大小排序取前 k 个特征向量。模板代码如下:
Eigen::MatrixXd data = /* 每行一个样本,每列一个特征 */; Eigen::MatrixXd centered = data.rowwise() - data.colwise().mean(); Eigen::MatrixXd cov = (centered.adjoint() * centered) / double(data.rows() - 1); Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> solver(cov); Eigen::VectorXd eigenvalues = solver.eigenvalues(); Eigen::MatrixXd eigenvectors = solver.eigenvectors();注意eigenvalues()返回的特征值是升序排列的,取前几个主成分时要从后往前取,或者直接reverse()一下。这是我第一次实现 PCA 时踩过的老坑。
5.4 最小二乘拟合:从正规方程到 SVD
最后是最小二乘问题,在标定、测量、机器学习里的线性回归等场景中无处不在。问题形式是:在Ax ≈ b无精确解时,找到使残差范数最小的 x。最直观的方法是解正规方程AᵀAx = Aᵀb,但这种方法在 A 的条件数较大时容易损失精度。更建议用 SVD:
Eigen::MatrixXd A; Eigen::VectorXd b; // 填充数据... Eigen::JacobiSVD<Eigen::MatrixXd> svd(A, Eigen::ComputeThinU | Eigen::ComputeThinV); Eigen::VectorXd x = svd.solve(b);SVD 慢一点,但稳定,尤其在数据可能线性相关的时候不会直接炸掉。如果数据量巨大,再考虑LDLT或者ConjugateGradient等方法。工程上的选择逻辑通常是:数据量小、要求精度高用 SVD;数据量大、病态程度低用 QR 或者直接上HouseholderQR。
6. 项目接入中的常见问题与避坑技巧
Eigen 学会基础操作只是第一步,真正上手项目时,有很多细枝末节能把人逼疯。下面整理的是我实际遇到过的、也是社区里问得最多的问题。
6.1 编译报错:Eigen/Dense: No such file or directory
这个问题基本都是 include 路径没写对。你需要确认你的-I参数指向的目录里直接能看到Eigen子目录。如果头文件在/usr/include/eigen3/Eigen/Dense,那么-I应该填/usr/include/eigen3,而不是/usr/include/eigen3/Eigen。这是新手最容易犯的路径错误,多一层目录就查不出来。
如果是 apt 安装的 Eigen,在 Ubuntu 上还可能遇到/usr/include/eigen3和/usr/include/eigen3/Eigen的软链接问题。我个人的处理方式是:直接在CMakeLists.txt里find_package(Eigen3 REQUIRED),让 CMake 自动处理这些目录细节。
6.2 性能陷阱:在热点循环里使用了动态矩阵
Eigen 虽然很快,但是如果你在 for 循环里反复构造同一个维度的MatrixXd,会带来大量动态内存分配,性能直接掉几个数量级。正确做法是:能编译期确定维度的就用固定大小类型,比如Matrix3d、Vector4d;必须动态大小的,尽量在循环外先resize(),循环内复用。还有一点,不要在循环里用A * B这种临时表达式去做重复计算,可以先定义MatrixXd C = A * B;放在循环外面复用。
6.3 对齐错误(Aligned Allocation)导致的段错误
这是个经典问题。早期 Eigen 版本(3.3 系列依然存在)对固定大小的 SIMD 向量化类型(比如Vector4d)有内存对齐要求。如果你在普通的结构体中定义这些类型,并且用new动态分配或放入 STL 容器中,可能触发段错误。解决办法有三种:
- 使用
Eigen::aligned_allocator配合 STL 容器:
std::vector<Eigen::Vector4d, Eigen::aligned_allocator<Eigen::Vector4d>> vec;- 定义结构体时,给成员加上对齐声明宏
EIGEN_MAKE_ALIGNED_OPERATOR_NEW,这是老工程常用的写法,但 3.3.9 里依然保留着。 - 最省心的办法是直接改用动态大小类型,如
VectorXd,动态分配时 Eigen 会自动保证对齐。
我自己的经验是:凡是在结构体里放Vector4d、Matrix2d这类固定大小的 SIMD 类型时,一律加上对齐宏或者干脆换成动态类型,不要在编译期省这点时间,不然 debug 起来相当难受。
6.4 编译错误提示包含C+11、C++14之类的字符
Eigen 3.3.9 从 C++11 以后都能正常使用,但部分高级特性(比如Tensor模块的特定接口)对新标准有要求。如果在编译时收到这类提示,多半是你编译器标准设置得太低,或者因为项目启用了旧标准导致某些模板特化无法匹配。把 CMake 中的CMAKE_CXX_STANDARD设成 11 或 14 基本就能解决问题。如果项目本身因为历史原因不能升,那就尽量避免使用新版 Eigen 特有的接口,用基础的Dense模块功能通常是没问题的。
6.5 误把 R 语言的 eigen() 和 C++ Eigen 混用
这个前面已经讲过。如果你在 R 脚本里用了eigen(),然后在 C++ 代码里也看到Eigen::,不要试图在两边互相传数据。正确的做法是:要么全在 R 里处理数据,要么全在 C++ 里处理,两者之间通过文件、Rcpp 桥接层或者 socket 通信交换结果。用 R 调 C++ 时,RcppEigen这个包是官方推荐的接口,但那是进阶话题,这里点到为止。
7. 一点扩展心得:从 eigen-3.3.9 看老版本库的工程价值观
写了这么多,最后聊聊我对老版本库的一些理解。在开源软件快速迭代的今天,很多项目习惯了一有新版就升级,但 Eigne 3.3.9 这种老版本长期存在其实代表了工程界的一种务实态度——"能用、稳定、可控" 有时候比 "最新、最快、更多特性" 更重要。
我在实际维护一个机器人定位项目时,遇到过系统自带的 Eigen 版本和算法库版本冲突的问题。当时算法库要求 3.3.9,而编译环境里某个依赖库链进了 3.4 的头文件,导致奇怪的模板实例化错误。最终方案是把 Eigen 3.3.9 以源码形式放进工程里,用 include 路径隔离,防止系统环境的干扰。这个方案虽然"土",但非常可靠。也正因为有过这次经历,我后来接手任何 C++ 项目,都会先看一眼它锁定的 Eigen 版本号,心里有数。
如果你手头正要做选型,我的建议是:新项目可以从 3.4 或者 latest 版本起步,因为新特性终究会变成主流;老项目、课程实验、对稳定性要求极高的工业项目,选择 3.3.9 是完全合理的,它能够满足绝大多数矩阵计算需求,且社区资料丰富、踩坑经验充足,出了问题搜一搜基本都能找到答案。说到底,工具是拿来解决问题的,能把问题稳定解决的就是好工具。
本文还有配套的精品资源,点击获取