Eigen3 矩阵乘法性能

use*_*816 3 c++ performance matrix eigen visual-studio-2013

注意:我也在 E​​igen 论坛上发布了此内容

我想用 3x3 矩阵预乘 3xN 矩阵,即变换 3D 点,如 p_dest = T * p_source

初始化矩阵后:

Eigen::Matrix<double, 3, Eigen::Dynamic> points = Eigen::Matrix<double, 3, Eigen::Dynamic>::Random(3, NUMCOLS);
Eigen::Matrix<double, 3, Eigen::Dynamic> dest = Eigen::Matrix<double, 3, Eigen::Dynamic>(3, NUMCOLS);
int NT = 100;
Run Code Online (Sandbox Code Playgroud)

我评估过这两个版本

// eigen direct multiplication
for (int i = 0; i < NT; i++){
  Eigen::Matrix3d T = Eigen::Matrix3d::Random();
  dest.noalias() = T * points;
}
Run Code Online (Sandbox Code Playgroud)

// col multiplication
for (int i = 0; i < NT; i++){
  Eigen::Matrix3d T = Eigen::Matrix3d::Random();
  for (int c = 0; c < points.cols(); c++){
    dest.col(c) = T * points.col(c);
  }
}
Run Code Online (Sandbox Code Playgroud)

NT 重复只是为了计算平均时间

我很惊讶逐列乘法比直接乘法快大约4/5 倍(如果我不使用直接乘法甚至会更慢.noalias(),但这很好,因为它正在执行临时副本)我已经尝试将 NUMCOLS 从 0 更改为 1000000,并且关系是线性的。

我正在使用 Visual Studio 2013 并在发行版中进行编译

下图在 X 上显示了矩阵的列数,在 Y 上显示了单个操作的平均时间,蓝色是 col 与 col 乘法,红色是矩阵乘法

图像

有什么建议为什么会发生这种情况吗?

Avi*_*urg 5

简短回答

您正在对 col 乘法版本中的惰性(因此缺乏)评估进行计时,而对直接版本中的惰性(但评估)评估进行计时。

长答案

让我们看一下完整的MCVE ,而不是代码片段。首先是“你是”版本:

void ColMult(Matrix3Xd& dest, Matrix3Xd& points)
{
    Eigen::Matrix3d T = Eigen::Matrix3d::Random();
    for (int c = 0; c < points.cols(); c++){
        dest.col(c) = T * points.col(c);
    }
}

void EigenDirect(Matrix3Xd& dest, Matrix3Xd& points)
{
    Eigen::Matrix3d T = Eigen::Matrix3d::Random();
    dest.noalias() = T * points;
}

int main(int argc, char *argv[])
{
    srand(time(NULL));

    int NUMCOLS = 100000 + rand();

    Matrix3Xd points = Matrix3Xd::Random(3, NUMCOLS);
    Matrix3Xd dest   = Matrix3Xd(3, NUMCOLS);
    Matrix3Xd dest2  = Matrix3Xd(3, NUMCOLS);
    int NT = 200;
    // eigen direct multiplication
    auto beg1 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < NT; i++)
    {
        EigenDirect(dest, points);
    }
    auto end1 = std::chrono::high_resolution_clock::now();

    std::chrono::duration<double> elapsed_seconds = end1-beg1;

    // col multiplication
    auto beg2 = std::chrono::high_resolution_clock::now();
    for(int i = 0; i < NT; i++)
    {
        ColMult(dest2, points);
    }

    auto end2 = std::chrono::high_resolution_clock::now();

    std::chrono::duration<double> elapsed_seconds2 = end2-beg2;
    std::cout << "Direct time: " << elapsed_seconds.count() << "\n";
    std::cout << "Col time: " << elapsed_seconds2.count() << "\n";

    std::cout << "Eigen speedup: " << elapsed_seconds2.count() / elapsed_seconds.count() << "\n\n";
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

通过这段代码(并且打开了 SSE),我得到:

Direct time: 0.449301
Col time: 0.10107
Eigen speedup: 0.224949
Run Code Online (Sandbox Code Playgroud)

你抱怨的同样是 4-5 的减速。为什么?!?!在得到答案之前,让我们稍微修改一下代码,以便将dest矩阵发送到ostream. 在计时器的std::ostream outPut(0);开始处main()和结束之前添加outPut << dest << "\n\n";outPut << dest2 << "\n\n";。不会std::ostream outPut(0);输出任何内容(我很确定 badbit 已设置),但它确实会导致调用特征值operator<<会强制对矩阵求值。

注意:如果我们使用outPut << dest(1,1)then ,dest则其计算结果仅足以输出 col 乘法方法中的单个元素。

然后我们得到

Direct time: 0.447298
Col time: 0.681456
Eigen speedup: 1.52349
Run Code Online (Sandbox Code Playgroud)

结果正如预期的那样。请注意,Eigen direct 方法花费了完全相同的时间(这意味着即使没有添加 ,评估也会发生ostream),而 col 方法突然花费了更长的时间。