C++ Eigen库:Ref<>的性能开销

3
我正在编写一个通用库,使用Eigen库进行计算力学,主要处理6x6大小的矩阵和6x1大小的向量。考虑使用Eigen :: Ref <>模板,使其也可用于段和块,如文档所述http://eigen.tuxfamily.org/dox/TopicFunctionTakingEigenTypes.htmlCorrect usage of the Eigen::Ref<> class 然而,小型性能比较表明,与标准C ++引用相比,Eigen :: Ref在这种小型函数中具有相当大的开销:
#include <ctime>
#include <iostream>
#include "Eigen/Core"


Eigen::Matrix<double, 6, 6> testRef(const Eigen::Ref<const Eigen::Matrix<double, 6, 6>>& A)
{
    Eigen::Matrix<double, 6, 6> temp = (A * A) * A;
    temp.diagonal().setOnes();
    return temp;
}

Eigen::Matrix<double, 6, 6> testNoRef(const Eigen::Matrix<double, 6, 6>& A)
{
    Eigen::Matrix<double, 6, 6> temp = (A * A) * A; 
    temp.diagonal().setOnes();
    return temp;
}


int main(){

  using namespace std;

  int cycles = 10000000;
  Eigen::Matrix<double, 6, 6> testMat;
  testMat = Eigen::Matrix<double, 6, 6>::Ones();

  clock_t begin = clock();

  for(int i = 0; i < cycles; i++)
      testMat = testRef(testMat);

  clock_t end = clock();


  double elapsed_secs = double(end - begin) / CLOCKS_PER_SEC;

  std::cout << "Ref: " << elapsed_secs << std::endl;

  begin = clock();

  for(int i = 0; i < cycles; i++)
      testMat = testNoRef(testMat);
  end = clock();

  elapsed_secs = double(end - begin) / CLOCKS_PER_SEC;

  std::cout << "noRef : " << elapsed_secs << std::endl;


    return 0;
}

使用 gcc -O3 输出:

Ref: 1.64066
noRef : 1.1281

看起来 Eigen::Ref 有相当大的开销,至少在实际计算工作量较低的情况下是如此。 另一方面,如果传递块或片段,则使用 const Eigen::Matrix<double, 6, 6>& A 的方法会导致不必要的复制:

#include <Eigen/Core>
#include <iostream>


void test( const Eigen::Vector3d& a)
{
    std::cout << "addr in function " << &a << std::endl;
}

int main () {

    Eigen::Vector3d aa;
    aa << 1,2,3;
    std::cout << "addr outside function " << &aa << std::endl;

    test ( aa ) ;
    test ( aa.head(3) ) ;


    return 0;
}

输出:

addr outside function 0x7fff85d75960
addr in function 0x7fff85d75960
addr in function 0x7fff85d75980

所以这种方法在一般情况下被排除了。相反,可以使用Eigen::MatrixBase来制作函数模板,如文档中所述。然而,对于大型库来说,这似乎效率低下,并且无法适应固定大小的矩阵(6x6、6x1),就像我遇到的情况一样。还有其他替代方法吗?对于大型通用库,有什么一般建议吗?提前感谢您!编辑:根据评论中的建议修改了第一个基准示例。

2
无法在优化状态下重现,我得到了Ref: 0.069 noRef: 0.069。如果性能在没有优化的情况下很重要,那么Eigen通常会有巨大的开销,但这在大多数情况下都会消失。 - PeterT
1
你是否启用了优化测试(例如 -O2)?如果没有,你的结果是不可信的,但如果... 你的测试函数没有副作用。这带来了一个危险,即 Eigen::Matrix<double, 6, 6> temp = (A * A); 可能会被优化掉。你应该返回值,将它们存储在一个 vector 中,并在测量后打印它们,以防止它们因为优化而“消失”。查看汇编代码也可以帮助揭示你的代码实际上到达了二进制文件中的哪部分。 - Scheff's Cat
基准测试无效,因为一旦启用优化,编译器就可以完全删除函数的主体。 - ggael
我修改了基准示例并启用了优化,但仍存在差异。 - mneuner
@macmallow 我仍然无法可靠地使它变慢。VS2017现在使它有点慢,但如果我不断重复测试,则同一测试中速度的差异似乎比两者之间的平均值差异更大。在gcc中,我现在大多数时间都会发现Ref更快。我想现在是时候查看编译器版本、CPU和Eigen版本了。 - PeterT
@PeterT 当然可以:g++ (GCC) 8.2.1 2018083,Eigen 3.3.5 和 Intel© Core™ i7-7820X CPU @ 3.60GHz × 8。 - mneuner
1个回答

4

使用 Ref<> 相比于 Matrix,你会失去两个信息:

  1. 你失去了输入数据是内存对齐的知识。
  2. 你失去了编译时知道列是按顺序存储的知识(因此两列之间相隔6个双精度浮点数)。

这是通用性和最高性能之间的经典折衷。


我明白了。那么,我可以得出结论,只要排除临时副本,我应该尽可能地坚持使用C++引用,并仅在必要时使用Ref<>实现函数? - mneuner

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