草庐IT

c++ - Eigen 3.3.0 与 3.2.10 的性能回归?

coder 2024-02-05 原文

我们正处于 porting our codebase 的过程中转到 Eigen 3.3(所有 32 字节对齐问题都是一项艰巨的任务)。然而,有几个地方的性能似乎受到了严重影响,这与预期相反(鉴于对 FMA 和 AVX 的额外支持,我期待一些加速......)。这些包括特征值分解和 matrix*matrix.transpose()*vector 产品。我已经编写了两个最小的工作示例来进行演示。

所有测试都在最新的 Arch Linux 系统上运行,使用 Intel Core i7-4930K CPU (3.40GHz),并使用 g++ 版本 6.2.1 编译。

1。特征值分解:

使用 Eigen 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;
}

测试结果:

  • eigen-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
    
  • Eigen 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 保持性能的方法,我很想知道!

2。矩阵*矩阵.transpose()* vector 积:

这个特殊的例子在 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;
}

测试结果:

  • eigen-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
    
  • Eigen 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++-O3g++ 编译,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) 部分加上括号,两个 Eigen 版本的执行时间都减少到 0.020 秒左右(因此 Eigen 3.2.10 显然也有利于此案件)。至少这意味着我们可以继续从 Eigen 获得出色的性能!

与此同时,我会接受 ggael 的回答,这是我继续前进所需要知道的一切。

最佳答案

对于 EVD,我无法用 clang 重现。使用 gcc,您需要 -O3 来避免内联问题。然后,对于这两个编译器,Eigen 3.3 将提供 33% 的加速。

编辑 我之前关于matrix*matrix*vector 产品的回答是错误的。这是 Eigen 3.3.0 的一个缺点,将在 Eigen 3.3.1 中修复。作为记录,我把我之前的分析留在这里,它仍然部分有效:

As you noticed you should really add the parenthesis to perform two matrix*vector products instead of a big matrix*matrix product. Then the speed difference is easily explained by the fact that in 3.2, the nested matrix*matrix product is immediately evaluated (at nesting time), whereas in 3.3 it is evaluated at evaluation time, that is in operator=. This means that in 3.2, the loop is equivalent to:

for (int n = 0; n < 50; ++n) {
  MatrixXf tmp = mat * mat.transpose();
  vec = tmp * VectorXf::Random(SIZE);
}

and thus the compiler can move tmp out of the loop. Production code should not rely on the compiler for this kind of task and rather explicitly moves constant expression outside loops.

这是真的,除了在实践中编译器不够聪明,无法将临时变量移出循环。

关于c++ - Eigen 3.3.0 与 3.2.10 的性能回归?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40805386/

有关c++ - Eigen 3.3.0 与 3.2.10 的性能回归?的更多相关文章

  1. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  2. ruby - 使用 `+=` 和 `send` 方法 - 2

    如何将send与+=一起使用?a=20;a.send"+=",10undefinedmethod`+='for20:Fixnuma=20;a+=10=>30 最佳答案 恐怕你不能。+=不是方法,而是语法糖。参见http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_expressions.html它说Incommonwithmanyotherlanguages,Rubyhasasyntacticshortcut:a=a+2maybewrittenasa+=2.你能做的最好的事情是:

  3. ruby - 如何计算 Liquid 中的变量 +1 - 2

    我对如何计算通过{%assignvar=0%}赋值的变量加一完全感到困惑。这应该是最简单的任务。到目前为止,这是我尝试过的:{%assignamount=0%}{%forvariantinproduct.variants%}{%assignamount=amount+1%}{%endfor%}Amount:{{amount}}结果总是0。也许我忽略了一些明显的东西。也许有更好的方法。我想要存档的只是获取运行的迭代次数。 最佳答案 因为{{incrementamount}}将输出您的变量值并且不会影响{%assign%}定义的变量,我

  4. 由于 libgmp.10.dylib 的问题,Ruby 2.2.0 无法运行 - 2

    我刚刚安装了带有RVM的Ruby2.2.0,并尝试使用它得到了这个:$rvmuse2.2.0--defaultUsing/Users/brandon/.rvm/gems/ruby-2.2.0dyld:Librarynotloaded:/usr/local/lib/libgmp.10.dylibReferencedfrom:/Users/brandon/.rvm/rubies/ruby-2.2.0/bin/rubyReason:Incompatiblelibraryversion:rubyrequiresversion13.0.0orlater,butlibgmp.10.dylibpro

  5. arrays - Ruby 数组 += vs 推送 - 2

    我有一个数组数组,想将元素附加到子数组。+=做我想做的,但我想了解为什么push不做。我期望的行为(并与+=一起工作):b=Array.new(3,[])b[0]+=["apple"]b[1]+=["orange"]b[2]+=["frog"]b=>[["苹果"],["橙子"],["Frog"]]通过推送,我将推送的元素附加到每个子数组(为什么?):a=Array.new(3,[])a[0].push("apple")a[1].push("orange")a[2].push("frog")a=>[[“苹果”、“橙子”、“Frog”]、[“苹果”、“橙子”、“Frog”]、[“苹果”、“

  6. Ruby 的数字方法性能 - 2

    我正在使用Ruby解决一些ProjectEuler问题,特别是这里我要讨论的问题25(Fibonacci数列中包含1000位数字的第一项的索引是多少?)。起初,我使用的是Ruby2.2.3,我将问题编码为:number=3a=1b=2whileb.to_s.length但后来我发现2.4.2版本有一个名为digits的方法,这正是我需要的。我转换为代码:whileb.digits.length当我比较这两种方法时,digits慢得多。时间./025/problem025.rb0.13s用户0.02s系统80%cpu0.190总计./025/problem025.rb2.19s用户0.0

  7. ruby - Ruby 性能中的计时器 - 2

    我正在寻找一个用ruby​​演示计时器的在线示例,并发现了下面的代码。它按预期工作,但这个简单的程序使用30Mo内存(如Windows任务管理器中所示)和太多CPU有意义吗?非常感谢deftime_blockstart_time=Time.nowThread.new{yield}Time.now-start_timeenddefrepeat_every(seconds)whiletruedotime_spent=time_block{yield}#Tohandle-vesleepinteravalsleep(seconds-time_spent)iftime_spent

  8. ruby - ri 有空文件 – Ubuntu 11.10, Ruby 1.9 - 2

    我正在运行Ubuntu11.10并像这样安装Ruby1.9:$sudoapt-getinstallruby1.9rubygems一切都运行良好,但ri似乎有空文档。ri告诉我文档是空的,我必须安装它们。我执行此操作是因为我读到它会有所帮助:$rdoc--all--ri现在,当我尝试打开任何文档时:$riArrayNothingknownaboutArray我搜索的其他所有内容都是一样的。 最佳答案 这个呢?apt-getinstallri1.8编辑或者试试这个:(非rvm)geminstallrdocrdoc-datardoc-da

  9. ruby-on-rails - 如果条件与 &&,是否有任何性能提升 - 2

    如果用户是所有者,我有一个条件来检查说删除和文章。delete_articleifuser.owner?另一种方式是user.owner?&&delete_article选择它有什么好处还是它只是一种写作风格 最佳答案 性能不太可能成为该声明的问题。第一个要好得多-它更容易阅读。您future的自己和其他将开始编写代码的人会为此感谢您。 关于ruby-on-rails-如果条件与&&,是否有任何性能提升,我们在StackOverflow上找到一个类似的问题:

  10. += 的 Ruby 方法 - 2

    有没有办法让Ruby能够做这样的事情?classPlane@moved=0@x=0defx+=(v)#thisiserror@x+=v@moved+=1enddefto_s"moved#{@moved}times,currentxis#{@x}"endendplane=Plane.newplane.x+=5plane.x+=10putsplane.to_s#moved2times,currentxis15 最佳答案 您不能在Ruby中覆盖复合赋值运算符。任务在内部处理。您应该覆盖+,而不是+=。plane.a+=b与plane.a=

随机推荐