我正在研究加速大部分 C++ 代码的方法,这些代码具有用于计算雅可比的自动导数。这涉及在实际残差中做一些工作,但大部分工作(基于分析的执行时间)是计算雅可比。
这让我很惊讶,因为大多数雅可比是从 0 和 1 向前传播的,所以工作量应该是函数的 2-4 倍,而不是 10-12 倍。为了模拟大量的 jacobian 工作是什么样的,我做了一个 super 最小的例子,只有一个点积(而不是真实情况下的 sin、cos、sqrt 等),编译器应该能够优化为单个返回值:
#include <Eigen/Core>
#include <Eigen/Geometry>
using Array12d = Eigen::Matrix<double,12,1>;
double testReturnFirstDot(const Array12d& b)
{
Array12d a;
a.array() = 0.;
a(0) = 1.;
return a.dot(b);
}
应该和
一样double testReturnFirst(const Array12d& b)
{
return b(0);
}
我很失望地发现,在没有启用快速数学的情况下,GCC 8.2、Clang 6 或 MSVC 19 都无法对充满 0 的矩阵的天真点积进行任何优化。即使使用快速数学 (https://godbolt.org/z/GvPXFy),GCC 和 Clang 中的优化也很差(仍然涉及乘法和加法),并且 MSVC 根本不做任何优化。
我没有编译器方面的背景,但这有什么原因吗?我相当肯定,在大部分科学计算中,能够进行更好的常数传播/折叠将使更多优化变得明显,即使常数折叠本身并没有导致加速。
虽然我对解释为什么不在编译器方面这样做很感兴趣,但我也对我可以在实际方面做些什么来使我自己的代码在面对这些类型的模式时更快。
最佳答案
这是因为 Eigen 将您的代码显式矢量化为剩余 4 个组件寄存器中的 3 个 vmulpd、2 个 vaddpd 和 1 个水平缩减(假设为 AVX,仅使用 SSE,您将获得 6 个 mulpd 和 5 个 addpd)。与 -ffast-math
GCC 和 clang 可以删除最后 2 个 vmulpd 和 vaddpd(这就是他们所做的),但它们不能真正替换 Eigen 显式生成的剩余 vmulpd 和水平缩减。
如果你通过定义 EIGEN_DONT_VECTORIZE
来禁用 Eigen 的显式矢量化会怎样? ?然后你会得到你所期望的( https://godbolt.org/z/UQsoeH ),但其他代码段可能会变得慢得多。
如果你想在本地禁用显式矢量化并且不怕弄乱 Eigen 的内部,你可以引入一个 DontVectorize
Matrix
的选项并通过专门化 traits<>
禁用矢量化为此Matrix
类型:
static const int DontVectorize = 0x80000000;
namespace Eigen {
namespace internal {
template<typename _Scalar, int _Rows, int _Cols, int _MaxRows, int _MaxCols>
struct traits<Matrix<_Scalar, _Rows, _Cols, DontVectorize, _MaxRows, _MaxCols> >
: traits<Matrix<_Scalar, _Rows, _Cols> >
{
typedef traits<Matrix<_Scalar, _Rows, _Cols> > Base;
enum {
EvaluatorFlags = Base::EvaluatorFlags & ~PacketAccessBit
};
};
}
}
using ArrayS12d = Eigen::Matrix<double,12,1,DontVectorize>;
关于c++ - 为什么 C++ 编译器不做更好的常量折叠?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52113522/