如何加速Eigen库中的矩阵乘法?

7
我正在使用Eigen库学习两个大矩阵的简单乘法。相较于Matlab和Python,这种乘法似乎明显较慢。是否有任何方法可以使Eigen运算更快?
问题详情:
X: 随机1000 x 50000矩阵
Y: 随机50000 x 300矩阵
定时实验(在我2011年底的Macbook Pro上):
使用Matlab:X*Y需要约1.3秒
使用Enthought Python:numpy.dot(X, Y)需要约2.2秒
使用Eigen:X*Y需要约2.7秒
Eigen细节:
您可以获取我的Eigen代码(作为MEX函数):https://gist.github.com/michaelchughes/4742878 此MEX函数从Matlab中读取两个矩阵,并返回它们的乘积。
在没有矩阵乘法操作的情况下运行此MEX函数(即仅执行IO操作)会产生可忽略的开销,因此函数和Matlab之间的IO并不能解释性能差异。显然是实际的矩阵乘法操作造成了差别。
我使用这些优化标志通过g++进行编译:"-O3 -DNDEBUG"
我正在使用最新的稳定Eigen头文件(3.1.2)。
如何提高Eigen的性能?有人能够复制我看到的差距吗?
更新: 编译器似乎真的很重要。原始的Eigen计时是使用Apple XCode的g++版本llvm-g++-4.2完成的。
当我使用通过MacPorts下载的g++-4.7(相同的CXXOPTIMFLAGS)时,我得到了2.4秒而不是2.7秒。
任何其他关于如何更好地编译的建议都将不胜感激。
您还可以获取此实验的原始C ++代码:https://gist.github.com/michaelchughes/4747789 ./MatProdEigen 1000 50000 300

在 g++-4.7 下报告 2.4 秒


你知道它实现的是什么算法吗?看起来可能只是使用了一个糟糕的矩阵乘法算法。另外一个尝试的方法是启用自动向量化:http://gcc.gnu.org/projects/tree-ssa/vectorization.html(默认情况下未开启,我想...好吧,也许。不确定)。如果你在英特尔机器上,尝试使用英特尔编译器...我注意到它在优化方面总是胜过其他人。还可以参见这里http://eigen.tuxfamily.org/index.php?title=FAQ#Vectorization - thang
@thang:Eigen是为线性代数而设计的,所以如果使用的算法很糟糕,我会感到惊讶。根据您提供的链接,使用“-O3”优化标志默认启用了树形向量化,因此这不是问题。如果没有其他建议出现,我可能会尝试使用英特尔编译器。 - Mike Hughes
@MikeHuges,您也可以尝试将增长率作为矩阵大小的函数绘制图形,这可能会提供一些有关正在发生的情况的提示。这应该会给出它使用哪种算法的指示。或者,深入研究它们的源代码或文档。 - thang
嗨,我在我的机器上运行测试C++代码大约花了260秒的时间,我使用的是VS2012和Windows操作系统,我的处理器是i5-4570。在测试矩阵乘法时,也花费了我大约1.3秒的时间。这相当奇怪。 - user978112
3个回答

12

首先,在进行性能比较时,确保已禁用Turbo Boost(TB)。在我的系统上,使用来自MacPort的gcc 4.5并且没有启用Turbo Boost,我获得了3.5秒的结果,相当于8.4 GFLOPS,而我的2.3核心i7的理论峰值是9.2GFLOPS,所以结果还不错。

MatLab基于Intel MKL,并且根据报告的性能来看,它显然使用了多线程版本。一个小型库如Eigen很难在自己的CPU上击败Intel!

Numpy可以使用任何BLAS库,如Atlas、MKL、OpenBLAS、eigen-blas等。我猜在你的情况下,它使用的是快速的Atlas。

最后,以下是如何提高性能的方法:通过使用-fopenmp编译以启用Eigen的多线程功能。默认情况下,Eigen使用由OpenMP定义的默认线程数。不幸的是,此数字对应于逻辑核心的数量,而不是物理核心的数量,因此确保已禁用超线程或将OMP_NUM_THREADS环境变量定义为物理核心的数量。在这里,我得到了1.25秒的结果(未启用TB),并且启用TB后我得到了0.95秒的结果。


多线程的选择很好:Matlab的多线程似乎解释了大部分差异。当我使用“-singleCompThread”命令行选项时,Matlab的运行时间约为2.4秒,与Eigen相同。 - Mike Hughes
在这种情况下,我想不出任何好的理由为什么你会将OMP_NUM_THREADS设置为除逻辑核心数以外的任何值。如果你将它设置为启用HT的物理核心数,那么你只会闲置50%的CPU... - quant
1
基本上,HT允许在同一物理核心上运行两个线程,以隐藏内存延迟并增强指令流水线。然而,像Eigen中实现良好的矩阵乘积例程已经占据了近100%的算术单元,这意味着流水线已经完美,并且内存延迟已经被很好地隐藏了。在这种情况下,HT只会破坏性能。例如,两个线程将同时访问相同的L1缓存资源,从而取消L1缓存的好处。 - ggael
据我所知,Eigen只使用了SSE,而MKL则使用了AVX。这是一个两倍的损失。自那以后,Haswell推出了FMA。我不知道MKL是否支持FMA,但如果支持,那么这可能会再次导致两倍的损失(总共四倍)。我自己编写的GEMM代码使用OpenMP和FMA比Eigen快两倍以上。 - Z boson

2
Matlab之所以更快,是因为它使用了Intel MKL。Eigen也可以使用它(请参见这里),但你需要购买它。
尽管如此,Eigen可能会变慢的原因有很多。要比较Python、Matlab和Eigen,你需要在各自的语言中编写三个等效版本的操作。此外,请注意,Matlab会缓存结果,所以你需要从新的Matlab会话开始,以确保它的魔力不会欺骗你。
另外,Matlab的Mex开销并不存在。那里的OP报告称,新版本“修复”了这个问题,但如果所有开销都已完全清除,我会感到惊讶。

对于我的特定情况(使用R2011b),MEX调用的开销不是主要原因。为了验证,我编写了一个纯C++版本的测试,它给出了与我计时的MEX相同的时间(约2.4秒)。我还运行了我的MEX版本,只需通过注释掉执行矩阵乘积的代码行来完成IO。这个仅包含IO的部分(所有开销)运行时间为~0.001秒。 - Mike Hughes
此外,Matlab的“缓存结果”也似乎无法解释这些问题。我进行了许多新的启动(使用命令行界面),所有启动时间都在1.3秒左右。很明显是多线程造成的(请参见下面@ggael的帖子)。 - Mike Hughes

2
Eigen没有利用Intel Sandy Bridge架构引入的AVX指令集。这可能解释了Eigen和MATLAB之间大部分性能差异。我发现一个分支,它在https://bitbucket.org/benoitsteiner/eigen添加了对AVX的支持,但据我所知,它尚未合并到Eigen主干中。

是的,FMA会再乘以一个二。我不知道是否有任何库已经支持FMA了。 - Z boson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接