C ++数字代码(使用Eigen)出奇地慢

时间:2014-12-24 02:23:36

标签: c++ performance numeric eigen numerical

我是一名习惯于在MATLAB / Python / Julia中编写代码的物理学家,考虑将C ++用于一些性能敏感的代码。我编写了目前我感兴趣的性能最敏感的函数之一 - Rotne-Prager张量的计算 - 以简洁的基准测试形式,并用C ++,Julia和MATLAB实现。 C ++版本目前比Julia代码慢了8倍,这使我得出结论我写了一些真正可怕的C ++。代码如下:

#include "stdafx.h"
#include <iostream>
#include <random>
#include <chrono>
#include <Eigen/Dense>

using namespace Eigen;
using namespace std;
using namespace std::chrono;

Matrix3d outer_product_3d(Vector3d a, Vector3d b)
{
    Matrix3d m;
    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            m(i, j) = a(i) * b(j);
        }
    }
    return m;
}

MatrixXd rotne_prager(Vector3d coords[], double r, const int p)
{
    MatrixXd rpy = MatrixXd::Zero(3*(p+1),3*(p+1));
    Vector3d rvec;
    double Rij;
    double distance_ratio;
    Matrix3d I = Matrix3d::Identity(3, 3);
    for (int i = 0; i < p + 1; i++)
    {
        for (int j = 0; j < p + 1; j++)
        {
            rvec(0) = coords[j](0) - coords[i](0);
            rvec(1) = coords[j](1) - coords[i](1);
            rvec(2) = coords[j](2) - coords[i](2);
            Rij = sqrt(rvec(0)*rvec(0) + rvec(1)*rvec(1) + rvec(2)*rvec(2));
            distance_ratio = r / Rij;
            if (Rij > 2 * r)
            {
                rpy.block(3 * (i + 1) - 3, 3 * (j + 1) - 3, 3, 3) =
                    0.75 * distance_ratio * ((1.0 + 2.0 / 3.0 * distance_ratio * distance_ratio) * I
                    + (1.0 - 2.0*distance_ratio * distance_ratio) * outer_product_3d(rvec, rvec));
            }
            else
            {
                rpy.block(3 * (i + 1) - 3, 3 * (j + 1) - 3, 3, 3) = 
                    (1.0 - 9.0 / 32.0 / distance_ratio) * I +
                    3.0 / 32.0 / distance_ratio * outer_product_3d(rvec, rvec);
            }
        }
    }
    return rpy;
}

int main()
{
    random_device rd;
    mt19937 gen(rd());
    uniform_real_distribution<> dis(0, 1);

    auto start = high_resolution_clock::now();

    const int p = 1000;
    Vector3d coords[p + 1];
    for (int i = 0; i < p + 1; i++)
    {
        coords[i](0) = dis(gen); coords[i](1) = dis(gen); coords[i](2) = dis(gen);
    }
    MatrixXd rpy = rotne_prager(coords, 0.01, p);
    auto end = high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
    cout << "p = " << p << ", time elapsed: " << duration << " ms" << endl;
}

Visual Studio分析工具告诉我在outer_product_3d函数中花费了大约三分之一的cpu时间(我认为这是相当合理的),其余的是在rotne_prager中。我没有在剖析器中看到任何明显错误的线条,而整个事情只需要花费更多时间,尽管结果实际上是正确的。

我很长一段时间没有写过任何C ++(而且我今天所写的内容都没有真正对性能敏感),所以我确信我做错了很多。但是,我已经浏览了Eigen文档并且没有发现任何真正的错误,所以如果有人能指出我的代码中可以从性能角度改进的区域,我将不胜感激。如果有人对此感兴趣,我还可以提供Julia代码(除了@inbounds之类的基本优化之外,它不包含任何特殊的性能技巧)。

1 个答案:

答案 0 :(得分:2)

首先,确保使用Visual Studio中的编译器优化ON(又称Release模式)进行基准测试。

其次,outer_product_3d函数的目的是什么,为什么不做rvec * rvec.transpose()?

最后,您可以按如下方式显着减少操作次数:

rpy.block<3,3>(3 * (i + 1) - 3, 3 * (j + 1) - 3) = ((1.0 - 2.0*distance_ratio * distance_ratio) * rvec) * rvec.transpose();
rpy.block<3,3>(3 * (i + 1) - 3, 3 * (j + 1) - 3).diagonal().array() += 0.75 * distance_ratio * ((1.0 + 2.0 / 3.0 * distance_ratio * distance_ratio);

在第一行中,您只需将缩放应用于3x3矩阵,即可避免将缩放应用于3x1矩阵。第二行保存了6个新增功能。这同样适用于else分支。