据我所了解的Eigen(这里),似乎operator=()
作为一种“障碍”,可以防止惰性评估——例如,它会导致Eigen停止返回表达式模板,并实际执行(优化)计算,将结果存储到=
的左侧。
这似乎意味着一个人的“编码风格”对性能有影响——即使用命名变量来存储中间计算结果可能会对性能产生负面影响,因为这会导致某些部分的计算“过早地”被评估。
为了验证我的直觉,我写了一个示例,并对结果感到惊讶(完整代码请看这里):
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();
}
上述内容提供了三种计算复数矩阵系数幅值的不同方法。1. `test1` 可以说是一步步地进行每个部分的计算。 2. `test2` 则通过一个表达式完成整个计算。 3. `test3` 采用了“混合”方法——使用了一定量的中间变量。
我本来期望由于 `test2` 将整个计算打包到一个表达式中,Eigen 应该能够利用它并对整个计算进行全局优化,从而提供最佳性能。
然而,结果却出人意料(显示的数字是每个测试执行1000次的总微秒数)。
test1_us: 154994
test2_us: 365231
test3_us: 36613
(这是使用g++ -O3编译的——详见gist。)
我原以为最快的版本(test2
)会是最慢的。 而我预计最慢的版本(test1
)居然排在中间。
因此,我的问题是:
- 为什么
test3
比其他方案表现得更好? - 除了深入研究汇编代码之外,有没有一种技术可以让人们了解Eigen实际上如何实现您的计算?
- 有没有一组准则可以遵循,以在您的Eigen代码中平衡性能和可读性(使用中间变量)?
在更复杂的计算中,将所有内容都写在一个表达式中可能会影响可读性,因此我希望找到正确的编写既易读又高效的代码的方法。
-O3
编译选项,并且没有捕获计算结果。完全有可能优化器会认识到funcN()
没有副作用并且优化掉整个计算过程。我相信您可以使用volatile
来辅助微基准测试。相关的Stack Overflow问题链接 - Steve Lorimerabs
版本是整数版本... - Marc Glisse