作为 Eigen 的新人,有一些事情我正在努力接受。
使用矩阵乘法,Eigen 默认创建一个临时值以避免混叠问题:
matA = matA * matA; // works fine (Eigen creates a temporary before assigning)
如果可以安全地假设没有锯齿,我们可以使用
.noalias()
来进行优化:
matB = matA * matA; // sub-optimal (due to unnecessary temporary)
matB.noalias() = matA * matA; // more efficient
因此,Eigen 默认情况下会避免做出不安全的假设,除非明确告知假设没有别名是安全的。到目前为止一切顺利。
但是,对于许多其他表达式,例如
a = a.transpose()
Eigen 默认情况下会做出不安全的假设(不存在别名问题),并且需要显式干预来避免这种不安全的假设:
a = a.transpose().eval(); // or alternatively: a.transposeInPlace()
因此,如果我们关心效率,那么仅在存在潜在混叠时小心是不够的,而在不存在潜在混叠时仅小心也是不够的。当存在潜在的混叠时,我们必须小心both,当没有潜在的混叠时,我们必须小心and,并根据表达式是否涉及矩阵乘法来决定是否需要特殊干预。 Eigen 界面中的这种“默认期望混合”是否有一些设计原理?
评论给出了问题的答案,总结如下:
Eigen 文档有效地提到您不应该这样做
a = a.transpose()
,但他们也提到 Eigen 使用运行时断言来检测这一点并退出并显示一条消息。问题在于,正如他们所说,一般来说,在编译时无法检测到别名,并且他们更注重效率。在创建临时对象时执行 b = a.transpose()
效率非常低。
本质上这是一种设计选择。一般来说,本征更喜欢效率,但矩阵乘积是意外混叠的常见来源(并且额外的副本通常比乘积便宜得多)。为了更好地转置,请使用
a.transposeInPlace();
。大多数其他表达式几乎没有别名问题,例如,在添加两个矩阵时,只有访问重叠(但不相等)的内存区域才会出现问题。
理由很简单,产品比临时创建 + O(n^2) 复制要贵得多。是的,Eigen 就像任何其他 BLAS 库一样,使用 O(n^3) 矩阵乘积算法。理论上,更高效的算法可以带来巨大的乘积(n>3000 甚至更多),更重要的是,它们会在浮点运算(浮点/双精度)时产生巨大且不可接受的舍入误差。
这个决定是在 Eigen 开发的早期阶段做出的,10 年后我宁愿无条件假设没有混叠以保持一致性。