我们正处于porting our codebase到Eigen 3.3的过程中(相当于所有32字节对齐问题的承诺)。然而,有一些地方的表现似乎受到严重影响,与预期相反(考虑到对FMA和AVX的额外支持,我期待一些加速......)。这些包括特征值分解和矩阵* matrix.transpose()*矢量积。我写了两个最小的工作示例来演示。
所有测试均在最新的Arch Linux系统上运行,使用Intel Core i7-4930K CPU(3.40GHz),并使用g ++版本6.2.1进行编译。
一个简单的自伴随特征值分解与本征3.3.0一样长,与3.2.10一样长。
档案test_eigen_EVD.cpp
:
#define EIGEN_DONT_PARALLELIZE
#include <Eigen/Dense>
#include <Eigen/Eigenvalues>
#define SIZE 200
using namespace Eigen;
int main (int argc, char* argv[])
{
MatrixXf mat = MatrixXf::Random(SIZE,SIZE);
SelfAdjointEigenSolver<MatrixXf> eig;
for (int n = 0; n < 1000; ++n)
eig.compute (mat);
return 0;
}
测试结果:
固有3.2.10:
g++ -march=native -O2 -DNDEBUG -isystem eigen-3.2.10 test_eigen_EVD.cpp -o test_eigen_EVD && time ./test_eigen_EVD
real 0m5.136s
user 0m5.133s
sys 0m0.000s
固有3.3.0:
g++ -march=native -O2 -DNDEBUG -isystem eigen-3.3.0 test_eigen_EVD.cpp -o test_eigen_EVD && time ./test_eigen_EVD
real 0m11.008s
user 0m11.007s
sys 0m0.000s
不确定是什么导致这种情况,但是如果有人能看到使用Eigen 3.3保持性能的方法,我想知道它!
这个特殊的例子与Eigen 3.3.0相比要长200倍......
档案test_eigen_products.cpp
:
#define EIGEN_DONT_PARALLELIZE
#include <Eigen/Dense>
#define SIZE 200
using namespace Eigen;
int main (int argc, char* argv[])
{
MatrixXf mat = MatrixXf::Random(SIZE,SIZE);
VectorXf vec = VectorXf::Random(SIZE);
for (int n = 0; n < 50; ++n)
vec = mat * mat.transpose() * VectorXf::Random(SIZE);
return vec[0] == 0.0;
}
测试结果:
固有3.2.10:
g++ -march=native -O2 -DNDEBUG -isystem eigen-3.2.10 test_eigen_products.cpp -o test_eigen_products && time ./test_eigen_products
real 0m0.040s
user 0m0.037s
sys 0m0.000s
固有3.3.0:
g++ -march=native -O2 -DNDEBUG -isystem eigen-3.3.0 test_eigen_products.cpp -o test_eigen_products && time ./test_eigen_products
real 0m8.112s
user 0m7.700s
sys 0m0.410s
在循环中的行中添加括号,如下所示:
vec = mat * ( mat.transpose() * VectorXf::Random(SIZE) );
产生了巨大的差异,两个Eigen版本的表现同样出色(实际上3.3.0稍好一些),并且比没有括号的3.2.10情况更快。所以有一个修复。不过,奇怪的是,3.3.0会因此而如此挣扎。
我不知道这是否是一个错误,但我想这是值得报道,以防这是需要修复的事情。或许我只是做错了......
任何想法都赞赏。 干杯, 唐纳德。
作为pointed out by ggael,如果使用clang++
或-O3
使用g++
进行编译,则Eigen 3.3中的EVD会更快。所以问题1已修复。
问题2并不是真正的问题,因为我可以使用括号来强制执行最有效的操作顺序。但仅仅是为了完整性:在评估这些操作时似乎存在某种缺陷。 Eigen是一个令人难以置信的软件,我认为这可能值得修复。这是MWE的修改版本,只是为了表明它不太可能与第一个被淘汰的临时产品有关(至少据我所知):
#define EIGEN_DONT_PARALLELIZE
#include <Eigen/Dense>
#include <iostream>
#define SIZE 200
using namespace Eigen;
int main (int argc, char* argv[])
{
VectorXf vec (SIZE), vecsum (SIZE);
MatrixXf mat (SIZE,SIZE);
for (int n = 0; n < 50; ++n) {
mat = MatrixXf::Random(SIZE,SIZE);
vec = VectorXf::Random(SIZE);
vecsum += mat * mat.transpose() * VectorXf::Random(SIZE);
}
std::cout << vecsum.norm() << std::endl;
return 0;
}
在这个例子中,操作数都在循环中初始化,结果在vecsum
中累积,因此编译器无法预先计算任何内容,或者优化掉不必要的计算。这显示了完全相同的行为(这次使用clang++ -O3
(版本3.9.0)进行测试:
$ clang++ -march=native -O3 -DNDEBUG -isystem eigen-3.2.10 test_eigen_products.cpp -o test_eigen_products && time ./test_eigen_products
5467.82
real 0m0.060s
user 0m0.057s
sys 0m0.000s
$ clang++ -march=native -O3 -DNDEBUG -isystem eigen-3.3.0 test_eigen_products.cpp -o test_eigen_products && time ./test_eigen_products
5467.82
real 0m4.225s
user 0m3.873s
sys 0m0.350s
同样的结果,但执行时间差异很大。值得庆幸的是,通过将括号放在正确的位置可以很容易地解决这个问题,但在Eigen 3.3的运算评估中似乎确实存在回归。使用mat.transpose() * VectorXf::Random(SIZE)
部分周围的括号,两个本征版本的执行时间减少到大约0.020秒(因此在这种情况下,Eigen 3.2.10显然也有好处)。至少这意味着我们可以继续从Eigen中获得出色的表现!
与此同时,我会接受ggael的回答,这是我向前迈进所需要知道的全部内容。
答案 0 :(得分:2)
对于EVD,我不能用clang重现。使用gcc,您需要-O3
以避免内联问题。然后,使用这两个编译器,Eigen 3.3将提供33%的加速。
编辑我之前关于matrix*matrix*vector
产品的回答是错误的。这是Eigen 3.3.0的一个缺点,将在Eigen 3.3.1中修复。为了记录,我留在这里我之前的分析仍然部分有效:
正如您所注意到的,您应该添加括号来执行两个
matrix*vector
个产品,而非大型matrix*matrix
产品。 然后通过3.2中的事实很容易解释速度差异, 立即评估嵌套的matrix*matrix
产品(at 嵌套时间),而在3.3中,它是在评估时评估的 在operator=
。这意味着在3.2中,循环等同于:for (int n = 0; n < 50; ++n) { MatrixXf tmp = mat * mat.transpose(); vec = tmp * VectorXf::Random(SIZE); }
因此编译器可以将
tmp
移出循环。生产代码 不应该依赖编译器来完成这种任务 显式地将循环中的常量表达式移动。
这是事实,除了在实践中编译器不够智能以将临时移出循环。