我是一名习惯于在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之类的基本优化之外,它不包含任何特殊的性能技巧)。
答案 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
分支。