目录
概要
最近在学习Eigen 线性代数计算库的代码,对其中很多C++的模板定义不甚理解,作为一名资深码农甚是惭愧。对于解决一个向量加法表达式v3 = v0 + v1 + v2,传统的编程方式存在中间内存的申请与释放、多次循环遍历等空间和时间的低效问题,不符合Eigen的高性能计算需求,为了提升性能,Eigen用了很多模板元编程技术,本文总结了其中两个知识点
- 奇异递归模板模式(CRTP)
- 表达式模板(Expression Templates)
奇异递归模板模式(CRTP)
动机与原理
- 用父类提供统一的API 接口
- 在Compile编译阶段确定调用子类接口实现的静态多态能力(相对于运行线虚函数的动态多态提升了运行时性能)
- 实现原理如下图所示
表达式模板(Expression Templates)
动机与原理
- 延迟计算表达式,从而可以将表达式传递给函数参数,而不是只能传表达式的计算结果
- 节省表达式中间计算结果的临时存储空间,减少向量等线性代数计算的循环次数,从而减少整体计算的空间和时间成本
- 实现原理如下图所示
示例代码
上面的原理图看起来还是比较难以理解的,下面以求解一个向量加法赋值表达式:v3 = v0 + v1 + v2 的程序代码为例解析原理图中的涵义
#include <cassert>
#include <iostream>
#include <vector>
//CRTP中的基类模板
template <typename E>
class VecExpression {
public:
//通过将自己static_cast成为子类,调用子类的对应函数实现实现静态多态
double operator[](size_t i) const { return static_cast<E const&>(*this)[i]; }
size_t size() const { return static_cast<E const&>(*this).size(); }
};
//将自己作为基类模板参数的子类 - 对应表达式编译树中的叶节点
class Vec : public VecExpression<Vec> {
std::vector<double> elems;
public:
double operator[](size_t i) const { return elems[i]; }
double &operator[](size_t i) { return elems[i]; }
size_t size() const { return elems.size(); }
Vec(size_t n) : elems(n) {}
Vec(std::initializer_list<double>init){
for(auto i:init)
elems.push_back(i);
}
//赋值构造函数可以接受任意父类VecExpression的实例,并且进行表达式的展开(对应表达式编译树中的赋值运算符节点)
template <typename E>
Vec(VecExpression<E> const& vec) : elems(vec.size()) {
for (size_t i = 0; i != vec.size(); ++i) {
elems[i] = vec[i];
}
}
};
//将自己作为基类模板参数的子类 - 对应表达式编译树中的二元运算符输出的内部节点
//该结构的巧妙之处在于模板参数E1 E2可以是VecSum,从而形成VecSum<VecSum<VecSum ... > > >的嵌套结构,体现了表达式模板的精髓:将表达式计算改造成为了构造嵌套结构
template <typename E1, typename E2>
class VecSum : public VecExpression<VecSum<E1, E2> > {
E1 const& _u;
E2 const& _v;
public:
VecSum(E1 const& u, E2 const& v) : _u(u), _v(v) {
assert(u.size() == v.size());
}
double operator[](size_t i) const { return _u[i] + _v[i]; }
size_t size() const { return _v.size(); }
};
//对应编译树上的二元运算符,将加法表达式构造为VecSum<VecSum... > >的嵌套结构
template <typename E1, typename E2>
VecSum<E1,E2> const operator+(E1 const& u, E2 const& v) {
return VecSum<E1, E2>(u, v);
}
//主函数入口
int main() {
//创建3个叶子节点
Vec v0 = {1.0, 1.0, 1.0};
Vec v1 = {2.0, 2.0, 2.0};
Vec v2 = {3.0, 3.0, 3.0};
//构建表达式 v0 + v1 + v2,赋值给v3,编译阶段形成表达式树
Vec v3 = v0 + v1 + v2;
//输出结算结果
for (int i = 0; i < v3.size(); i ++) {
std::cout <<" "<< v3[i];
}
std::cout << std::endl;
}
编译程序:
g++ -I /usr/local/include/eigen3/ -Wall -std=c++11 -fpermissive -g vecAdd.cpp -o vecAdd
运行程序
./vecAdd
程序输出:
6 6 6
示例程序对应的CRTP和编译树

