【发布时间】:2016-06-06 13:22:37
【问题描述】:
从我读到的关于 Eigen (here) 的内容来看,operator=() 似乎充当了懒惰评估的“障碍”——例如它会导致 Eigen 停止返回表达式模板并实际执行(优化的)计算,将结果存储到 = 的左侧。
这似乎意味着一个人的“编码风格”会对性能产生影响——即使用命名变量来存储中间计算的结果可能会对性能产生负面影响,因为它会导致计算的某些部分被评估“太早了”。
为了验证我的直觉,我写了一个例子,结果让我大吃一惊 (full code here):
using ArrayXf = Eigen::Array <float, Eigen::Dynamic, Eigen::Dynamic>;
using ArrayXcf = Eigen::Array <std::complex<float>, Eigen::Dynamic, Eigen::Dynamic>;
float test1( const MatrixXcf & mat )
{
ArrayXcf arr = mat.array();
ArrayXcf conj = arr.conjugate();
ArrayXcf magc = arr * conj;
ArrayXf mag = magc.real();
return mag.sum();
}
float test2( const MatrixXcf & mat )
{
return ( mat.array() * mat.array().conjugate() ).real().sum();
}
float test3( const MatrixXcf & mat )
{
ArrayXcf magc = ( mat.array() * mat.array().conjugate() );
ArrayXf mag = magc.real();
return mag.sum();
}
上面给出了 3 种不同的方法来计算复值矩阵中的系数大小之和。
-
test1有点像“一次一步”进行计算的每个部分。 -
test2在一个表达式中完成整个计算。 -
test3采用“混合”方法——使用一些中间变量。
我有点期待,由于 test2 将整个计算打包到一个表达式中,Eigen 将能够利用这一点并全局优化整个计算,从而提供最佳性能。
但是,结果令人惊讶(显示的数字是每个测试执行 1000 次的总微秒数):
test1_us: 154994
test2_us: 365231
test3_us: 36613
(这是使用 g++ -O3 编译的——有关详细信息,请参阅 the gist。)
我预计最快的版本 (test2) 实际上是最慢的。另外,我预计最慢的版本(test1)实际上在中间。
所以,我的问题是:
- 为什么
test3的性能比替代品好很多? - 是否可以使用一种技术(无需深入研究汇编代码)来了解 Eigen 是如何实际实现您的计算的?
- 是否有一套指导方针可以在您的 Eigen 代码中实现性能和可读性(使用中间变量)之间的良好权衡?
在更复杂的计算中,在一个表达式中执行所有操作可能会妨碍可读性,因此我有兴趣找到编写可读性和高性能代码的正确方法。
【问题讨论】:
-
我不是优化专家,但考虑到您使用
-O3编译并且没有捕获任何计算结果,我会怀疑您的结果。优化器将认识到funcN()没有副作用并优化整个计算是完全可行的。我相信您可以使用volatile来帮助进行微基准测试。 relevant SO question -
请注意,使用最近的编译器,程序总是中止。它只通过较旧的编译器,因为调用的
abs版本是整数版本...
标签: c++ performance eigen readability