C++的Eigen库如何比专门的供应商库表现更好?

51

1
这些基准测试很有趣,我承认一开始对它们持怀疑态度。当然,有可能有人在ATLAS上进行了激进的改进,但我想知道你提供的图表是否依赖于某些不寻常的特殊情况。我之所以问这个问题是因为我已经使用ATLAS多年了,而且还与其他使用ATLAS的人合作和通信过(除非我误解了基准测试),我从未听说过像这样的基准测试。但是,也许今天我会学到一些东西。 - thb
1
@thb 我完全同意你的想法。我可以轻易地相信Atlas可能比MKL慢,但比Eigen慢这么多?! - Anycorn
在提供的图表中,Eigen 在某些比较中会输掉小矩阵尺寸。我认为你应该能够运行基准测试并对其进行分析 - 以了解性能差异的原因。 - SigTerm
1
奇怪的是,对于矩阵维度使用了对数缩放。在我看来这毫无意义。很明显,对于小尺寸的矩阵,无法达到峰值性能。通常人们关心的是,当维度增加时,“峰值性能”可以达到多快。我使用ATLAS基准套件比较了一些BLAS库(包括Eigen)在Intel Core-2-Duo上的DGEMM:http://apfel.mathematik.uni-ulm.de/~lehn/sghpc/gemm/page14/index.html我在其他架构上得到了类似的结果。在每种情况下,MKL和ATLAS都比Eigen实现了更高的性能。 - Michael Lehn
6个回答

40
Eigen具有惰性求值特性。如Eigen如何与BLAS/LAPACK相比?所述:
对于涉及复杂表达式的操作,Eigen固有地比任何BLAS实现更快,因为它可以全局处理和优化整个操作--而BLAS强制程序员将复杂操作分成与BLAS固定函数API匹配的小步骤,这会由于引入临时变量而导致效率低下。例如,对于涉及两次调用BLAS level1例程的Y = aX + b Y操作的基准结果,而Eigen自动生成一个单个向量化循环。
基准测试的第二个图表中,是特别设计用于处理Y = a*X + b*Y的Eigen。不足为奇的是,一个库胜出了其创建基准测试。您会注意到,像矩阵乘法这样更通用的基准测试,并没有显示Eigen的任何优势。

1
我更关注gemms - A*A'明显比mkl快,仅比Goto略慢。对于N=1000,比mkl快近30%。 - Anycorn
你应该注意到,Goto已经有一段时间没有得到维护了。一些新的人接管了代码库,并迁移到了OpenBLAS - chrisaycock
这并不能解释相对于MKL的巨大优势。 - Anycorn
2
转置基本上是通过交换三个嵌套循环的顺序来处理的。加法的数量仅为N^2,而乘法的数量为N^3,非常微不足道。如果感兴趣,请访问http://www.netlib.org/blas/dgemm.f。 - Anycorn
1
@chrisaycock BLAS处理形式为C = betaC + alphaAB的操作。因此,如果您想要C = AB,则将beta设置为0,alpha设置为1。这里是一些最近的基准测试。 (http://www.mathematik.uni-ulm.de/~lehn/bench_FLENS/index.html) - Michael Lehn
显示剩余3条评论

39

基准测试的设计本就容易被误解

让我们来看一下矩阵乘法。Eigen网站上这个页面上提供的基准测试显示,在处理大型矩阵(n = 1000)时,使用Eigen(自带BLAS)与使用MKL的时间相似。但我在我的电脑上(搭载core i7的笔记本电脑)进行了Eigen 3.2.6和MKL 11.3的比较,发现单线程情况下MKL比Eigen快3倍,四线程情况下MKL比Eigen快10倍。这看起来就是完全不同的结论。其原因有两点:Eigen 3.2.6(自带的BLAS)不使用AVX指令集,而且似乎也没有很好地利用多线程。由于基准测试使用的CPU不支持AVX指令集,也没有启用多线程,所以这些问题都被隐藏了起来。

通常,这些C++库(如Eigen、Armadillo、Blaze)有两个优点:

  • 友好的运算符重载:您可以使用向量和矩阵的+、*等运算符。为了获得良好的性能,它们必须采用称为“智能模板表达式”的技术,以避免在减少计算时间(如y = alpha x1 + beta x2,其中y、x1和x2是向量)时引入临时变量,并在有用时引入它们(如A = B*C,其中A、B和C是矩阵)。它们还可以重新排序操作以减少计算量。例如,如果A、B、C是矩阵,则A*B*C可以计算为(A*B)*C或A*(B*C)。
  • 内部BLAS:为了计算两个矩阵的乘积,它们可以依靠自身的BLAS或外部提供的一个(MKL、OpenBLAS、ATLAS等)。对于大型矩阵的英特尔芯片,MKL几乎不可能被击败。对于小型矩阵,可以击败MKL,因为它并不是针对那种问题而设计的。
通常,当这些库提供针对MKL的基准测试时,它们通常会使用旧的硬件,并且不开启多线程,以便与MKL保持一致。它们可能还会比较BLAS级别1操作,如y = alpha x1 + beta x2和使用两个调用BLAS级别1函数的方法,但这是一件愚蠢的事情。
总之,这些库非常方便,因为它们重载了+和*运算符,如果没有失去性能,这很难做到。它们在这方面通常做得很好。但是,当它们给出基准测试结果,声称可以与其自己的BLAS相媲美或超越MKL时,请务必小心并进行自己的基准测试。你通常会得到不同的结果 ;-).

我已经有一段时间没有使用Eigen进行大型密集矩阵乘法了,但我认为您需要启用OpenMP支持才能获得多个线程。关于AVX,您是正确的,至少在我上次检查时是这样的。这就是为什么用自定义代码使用AVX和FMA不太难就能超越Eigen的GEMM。但无论如何,最大的密集矩阵乘法大多是一场虚荣的竞赛,因为大型稀疏矩阵乘法在实践中更加有用。 - Z boson
我还没有成功在Eigen上启用矩阵乘法的OpenMP。虽然我并没有尝试很努力,因为我不喜欢所有那些库(我使用包装器开发了自己的MKL)。对于实际有用的东西,它总是取决于您的领域。就我而言,在我的生产代码中,我认为我从未对任何矩阵进行过乘法运算;-)唯一有用的基准测试是在您的平台上解决您的问题。 - InsideLoop
2
我赞同。请自行进行基准测试。在这里http://www.mathematik.uni-ulm.de/~lehn/test_ublas/session8/page01.html和http://www.mathematik.uni-ulm.de/~lehn/test_blaze/session1/page01.html,我进行了一些基准测试,比较了Intel MKL、Eigen、BLAZE和我的实现。很难接近Intel MKL的性能。所有这些基准测试都是相当独立的。因此,您可以自己进行测试。您应该将“试图击败Intel MKL”视为一种爱好或自我教育目的。否则,这将会令人沮丧;-) - Michael Lehn
1
C++世界还有另一个让MFLOP基准测试误导人的技巧:对x轴进行对数缩放。这样可以隐藏大矩阵的结果。我从未见过C++以外的任何人这样做。 - Michael Lehn
我不同意这个答案,即“在不损失性能的情况下非常难做到”。Eigen通过堆叠操作的惰性求值(通过模板元编程)来优化操作,因此可以比连续声明操作更好地优化操作 - 在编译器实际看到代码之前。 然而,这需要程序员知道这个概念并且经常以不同的方式思考才能利用这种可能性。这也是我不同意“自己进行基准测试”的原因,因为您可能没有充分利用各自库的全部功能。 - DomTomCat
@DomTomCat:我知道惰性求值的所有好处。它允许您编写像数学运算一样的操作,并让Eigen库以良好的顺序堆叠这些操作。当我说这非常困难时,我从未意味着Eigen没有正确解决问题。实际上,Eigen做得非常好。但是,在执行这些“堆叠”操作时,Eigen可能会在某些平台上落后于MKL。如果您有能力,请进行自己的基准测试,并检查您的平台和矩阵大小是否存在差异。无论如何,您可以让Eigen调用MKL。 - InsideLoop

15

关于ATLAS和Eigen的比较

请参考Eigen邮件列表中这个帖子:

其中指出,例如在矩阵乘法方面,ATLAS的性能比Eigen快46%:

更多基准测试结果以及如何进行基准测试的详细信息,请参见以下链接:

编辑:

为我的“高性能计算软件基础”课程创建了一个名为ulmBLAS的小框架。它包含ATLAS基准测试套件,学生可以根据BLIS论文实现自己的矩阵乘法。您可以查看最终的基准测试结果,其中也测量了Eigen:

您可以使用 ulmBLAS 框架来制作自己的基准测试。

同时,请查看以下链接:


3
感谢Michael提供的ulmBlas开发页面,这些非常有趣 -- 令人着迷的是您在性能方面已经相当接近于MKL。 - davidhigh
这是一个很棒的答案,不幸的是大多数链接已经失效(或被禁止访问)。我知道这在这么多年后是可以预料的,但如果能更新/刷新一下这些材料,那就太好了。 - Lorah Attkins
1
@LorahAttkins 谢谢你告诉我,我没有注意到。链接现在应该可以使用了。 - Michael Lehn
我对你的讲座《高性能计算的软件基础》非常感兴趣,但是在谷歌上找不到相关信息。请问你能提供一下这个讲座的网址吗? - xiechao06
自从2020年夏季以来,我将讲座的主题更改为松散地讲述"构建自己的计算机并为其编写编译器"。所以上一次主题是"编写自己的硬件优化BLAS"是在2019年夏季。实验室会话的网站链接是mathematik.uni-ulm.de/numerik/hpc/ss19_hpc0。P.S. 我不得不限制对我的网站的访问,因为我使用了一些谷歌字体。在欧盟,这可能很昂贵(您可以使用谷歌翻译)https://www.heise.de/news/Neue-Abmahnwelle-Wieder-gehen-Schreiben-wegen-Google-Fonts-raus-7322064.html - Michael Lehn

5

通用代码可以很快,因为编译时函数评估(CTFE)允许选择最佳的寄存器阻塞策略(在CPU寄存器中存储的小临时子矩阵)。

Mir GLAS和Intel MKL比Eigen和OpenBLAS更快。Mir GLAS与Eigen相比更加通用。请参见基准测试reddit线程


2

根据您提供的链接,它似乎并没有始终优于其他库。从页面下方的图表可以看出这一点。因此,不同的库针对不同的用例进行了优化,并且不同的库在解决不同问题时更快。

这并不奇怪,因为通常您无法完美地优化所有用例。为一个特定操作进行优化通常会限制其他用例的优化选项。


1
但是核心-gemm-要么是最快的,要么是第二快的。例如,A*A'比mkl明显更快。 - Anycorn
3
Eigen具有优化长表达式的潜力,能够将整个表达式进行优化并生成相应的代码,因此性能可能会超越其他库。 没有类似C或FORTRAN的API具备这种功能。为了实现此功能,您需要使用C ++、Lisp或在CLR(如C#、F#等)上运行的语言。 - Kuba hasn't forgotten Monica

2

我之前曾将同样的问题发送到ATLAS邮件列表:

http://sourceforge.net/mailarchive/message.php?msg_id=28711667

ATLAS开发人员Clint不相信这些基准测试结果。他建议使用一些可靠的基准测试程序。我有空的时候会进行这种基准测试。

如果Eigen的BLAS功能确实比GotoBLAS / GotoBLAS、ATLAS、MKL等更快,那么它们应该提供标准的BLAS接口。这将允许将LAPACK与这样的Eigen-BLAS链接。在这种情况下,这也将是Matlab和其他软件的一个有趣的选择。


3
他们实际上提供了 BLAS 绑定,编译时有选项来构建它们。 - Anycorn
我知道。如果我有一些空闲时间,那么我会按照Clint的建议执行基准测试。然而,我更希望Eigen团队也这样做。任何模板化的C++库进行基准测试的问题在于关于必须使用哪个编译器版本和选项的无休止讨论。与此相比,构建像ATLAS这样的库在这方面更加稳健。因此,如果Eigen团队使用其最佳编译器配置并运行这样的“BLAS/LAPACK基准测试”,我想看看实际可能的基准测试结果。 - Michael Lehn
9
使用带有BLAS接口的Eigen非常愚蠢。Eigen之所以闪耀,是因为它可以优化整个表达式,通常生成可向量化的循环,其中包含所有操作。使用类似BLAS的接口,如果没有针对特定表达式的API可用,就必须为子表达式引入临时变量,这会降低性能。Eigen的性能取决于它利用了C++编译器的代码生成功能。除非C实现使用智能即时编译运行时(没有流行的),否则没有基于C的API可以做到这一点。 - Kuba hasn't forgotten Monica
5
你没有抓住重点:
  1. BLAS标准中定义的函数对于数值软件的性能至关重要。例如,形如(gemm)C = betaC + alphaA*B 的矩阵乘积。
  2. Eigen的开发者声称他们可以比ATLAS或GotoBLAS更快地执行这个操作。而且这是有争议的。
所以,高性能计算领域的人们感兴趣的问题是:如何检查Eigen是否能够比MKL、ATLAS和GotoBLAS更快地执行gemm操作。
- Michael Lehn
@KubaOber。此外,另一个问题是:Eigen团队发布的基准测试可信吗?他们使用了一些有问题的基准测试套件。而且很奇怪的是,他们声称超越了已经建立起来的库,但却没有通知这些库的维护者结果(ATLAS的人甚至从未听说过它们)。我的意思是,至少你必须给其他人机会回应和评论结果。 - Michael Lehn

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