Ami*_*ory 8 c++ move lazy-evaluation c++11
假设您编写了一个包含一些操作的矩阵类:
class matrix
{
public:
double operator()(size_t i, size_t j) const;
...
};
matrix operator*(const matrix &lhs, const matrix &rhs);
...
Run Code Online (Sandbox Code Playgroud)
推迟对某些矩阵表达式的评估是有意义的:m0*m1*m2*m3*m4(这是一系列的四个operator*调用)可以从使用动态编程矩阵链乘法算法中受益; 非常常见的m0*m1 t具有非常有效的dgemm实现,等等.
因此,它需要将实际计算推迟到需要时.这将以上内容更改为:
class matrix
{
private:
/*
* Pointer to an abstract base class - either an actual matrix,
* or an expression tree. */
std::shared_ptr<matrix_imp> m_imp;
public:
// Forces compaction -
double operator()(size_t i, size_t j) const;
...
};
/* Lazy; creates a matrix with an expression tree using the
* internals of lhs and rhs. */
matrix operator*(const matrix &lhs, const matrix &rhs);
...
Run Code Online (Sandbox Code Playgroud)
每个矩阵都包含一个指向基类对象的指针,该对象的范围可以从实矩阵到复杂的表达式树.每个操作都尝试使用对内部实现的最懒惰的更改来形成矩阵.有些操作别无选择,只能实际评估事物,压缩表达式树,并将内部实现设置为实际矩阵.
问题在于,在实践中,这在非常常见的情况下导致巨大的内存开销.假设您从文件中读取一个长而窄的矩阵x = x p X q,p >> q,将x t x存储在变量中,并丢弃x.使用延迟评估,内存为pq >> qq.将它们加载到循环中,这是一个严重的问题.(当然,在客户端代码调用的每次加载之后都可以强制压缩operator(),但是在没有算法验证的情况下要求这样做是丑陋且容易出错的.)
最初,我认为移动ctor是自动压缩的一个好点 - 它正是临时成为命名对象的点,而且它的命名对象会导致内存消耗增加,所以
matrix(matrix &&other); // <- Force compaction only here
Run Code Online (Sandbox Code Playgroud)
似乎解决了一切,例如,
auto res = // <- temp becoming named
a * // temp
b * // temp
c + // temp
2 * // temp
d;
Run Code Online (Sandbox Code Playgroud)
但它可以依靠吗?例如,考虑一下
matrix load_xtx(const string &f_name)
{
matrix x = ...
return x.t() * x;
}
auto xtx = load_xtx("foo.hdf5"); // (*)
Run Code Online (Sandbox Code Playgroud)
是否禁止编译器使用(*)类似于NRVO的编译器,即只是为了构建它?即使不是,编译器可能会在其他情况下优化掉事物吗?
由于“内部指针”方法无法提供延迟求值所需的所有灵活性,因此 C++ 数值库使用的典型解决方案是定义实现惰性求值机制的专用类。老SO问题LazyvaluationinC++及其最佳答案展示了这种设计的基础知识和一些示例代码。
虽然我不是专家,但我认为这种架构的很好的例子是数值库 Eigen (这里有一些关于其实现的细节)和 Blitz++,它们严重依赖于模板(我没有在网络上找到说明其内部结构的更新文档,但是本文描述了其引擎的某些部分,并且还提供了“表达式模板”技术的更广泛的概述)。
| 归档时间: |
|
| 查看次数: |
91 次 |
| 最近记录: |