我目前正在编写很多接受块和表达式作为输入的函数。我通常发现使用 Refs 更容易,因为它们简单、轻量级,而且也很容易确保传入的表达式满足特定的形状(例如向量)。
同时,我想这一定有某种缺点,否则使用模板
MatrixBase<Derived>
传递参数的方法就不会存在。不过,我找不到任何讨论这个主题的帖子。
所以我在这里问:使用 Refs 代替模板化函数参数有什么实际缺点?
常用的
Ref
有两种类型,用于输出或输入输出参数的可变 Ref<Matrix<…>>
和用于输入参数的不可变 Ref<const Matrix<…>>
。它们的行为实际上有点不同。
两者都保证输入矩阵或向量在编译时的内部步幅为 1,这意味着至少在一列中的元素彼此相邻。这允许矢量化。
然而,实现这一目标的方式有所不同。如果引用的块具有不同的步幅,则可变版本根本无法编译。不可变版本可能会创建一个临时副本。这可能会对性能和正确性产生影响。
考虑一下:
double sum(const Eigen::Ref<const Eigen::VectorXd>& in)
{ return in.sum(); }
double foo()
{
Eigen::VectorXcd x = …;
return sum(x.real());
}
x.real()
创建复值矩阵的视图。由于复数值以实部和虚部的交错格式存储,因此该视图的内部步幅为 2。因此 Ref
对象的构造函数将在内部分配一个 VectorXd
并将值复制到其中。
同样的情况也发生在这里:
double foo()
{
Eigen::MatrixXd x = …;
return sum(x.row(0));
}
但对于
x.col(0)
来说不会发生这种情况,因为 Eigen 默认使用列优先格式。
如果您使用
MatrixBase<Derived>
编写相同的函数,则总和本身将专门用于 x.real()
或 x.row()
表达式的固定或可变内部步幅。这将防止矢量化(好吧,也许仍然可以实现 x.real().sum()
的部分矢量化),但也避免了复制。
对于大多数用例,失去矢量化可能比创建副本更有利,但这需要测试。
当输入是任意表达式时,也会创建临时副本。例如这里:
double baz()
{
Eigen::VectorXd x = …;
return sum(x * 2.);
}
这里必须创建一个值为
x * 2.
的临时向量,因为转换无法传递给 sum
函数。如果 sum
接受了 MatrixBase<Derived>
,则代码将专门用于表达式对象,并且代码将像 (x * 2.).sum()
一样快速高效地运行。
一个小的性能问题是
Ref
不保证起始地址的对齐。 Eigen 将假设内容未对齐。如果您在没有 AVX 扩展的情况下进行编译,这主要是一个问题,因为 SSE 只能在保证对齐的情况下将内存加载和存储折叠到算术运算中。对于未对齐的内存加载/存储指令,非常旧的仅 SSE 硬件过去也非常慢,即使它们恰好在运行时对齐也是如此。
就正确性而言,如果您决定保留
Ref
对象(或指向 Ref
内容的指针)的时间比函数调用本身更长,这些临时副本可能会导致问题。我的代码曾经包含这样的函数:
using ConstMapType = Eigen::Map<const Eigen::MatrixXd, Eigen::OuterStride<>>;
// Never do this!
ConstMapType block_to_map(const Eigen::Ref<const Eigen::MatrixXd>& block)
{
return ConstMapType(block.data(), block.rows(), block.cols(),
Eigen::OuterStride<>(block.outerStride()));
}
如果您忘记了它是如何工作的,并且意外地使用某些导致临时副本的表达式调用它,则
data()
指针将在函数调用结束时悬空。