什么时候建议不要使用 Eigen::Ref 作为参数?

问题描述 投票:0回答:1

我目前正在编写很多接受块和表达式作为输入的函数。我通常发现使用 Refs 更容易,因为它们简单、轻量级,而且也很容易确保传入的表达式满足特定的形状(例如向量)。

同时,我想这一定有某种缺点,否则使用模板

MatrixBase<Derived>
传递参数的方法就不会存在。不过,我找不到任何讨论这个主题的帖子。

所以我在这里问:使用 Refs 代替模板化函数参数有什么实际缺点?

c++ templates eigen ref
1个回答
0
投票

常用的

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()
指针将在函数调用结束时悬空。

© www.soinside.com 2019 - 2024. All rights reserved.