【问题标题】:Eigen3: How to access matrix coefficients in performance critical operations?Eigen3:如何在性能关键操作中访问矩阵系数?
【发布时间】:2018-11-16 12:57:55
【问题描述】:

我正在尝试优化依赖 Eigen3 的 C++ 中的关键操作。我不清楚哪种类型的系数访问操作会导致运行时性能成本,或者编译器什么时候会做得很好。为了找出我困惑的根源,我在下面发布了一个以几种不同方式实现的示例,以及每种方式的一些假设。

这里有更多细节:

  • 矩阵 M 将在大部分程序中保持不变
  • critical_function 确实被调用了很多次,这就是它被内联的原因

有人能澄清一下哪种方法在性能方面最好吗?我可能对引用、取消引用等的影响成本感到困惑。

选项 1:直接访问矩阵系数

#include <Eigen/Dense>
class A{
    A(){
        // Assume M has the right numbers
    }

    // This function will be called many many times, inside loops
    inline void critical_function()
    {
        // Do many operations using M(1, 1), for example:
        double y = 1 / M(1, 1);
        // ... some more code using M(1, 1)
    }
private:
    Eigen::Matrix3d M;
};

假设

  • M(1,1) 会导致不断的取消引用,从而产生成本,因为计算偏移量时会添加循环(这不是数组,但不清楚编译器如何管理它)

选项 2:创建我们关心的系数的副本

#include <Eigen/Dense>
class A{
    A(){
        // Assume M has the right numbers
        x = M(1, 1);
    }

    // This function will be called many many times, inside loops
    inline void critical_function()
    {
        // Do many operations using x, for example:
        double y = 1 / x;
        // ... some more code using x
    }
private:
    double x;
    Eigen::Matrix3d M;
};

假设

  • 访问 x 生成的周期比访问 M(1, 1) 少,因此它比选项 1 更可取。
  • x 确实包含与 M(1,1) 相同的值,但存在确保此数据重复的重要风险,因此代码维护需要避免这种情况。

选项 3:利用引用

#include <Eigen/Dense>
class A{
    A(){
        // Assume M has the right numbers
    }

    // This function will be called many many times, inside loops
    inline void critical_function()
    {
        auto & x = M(1, 1);
        // Do many operations using x, for example:
        double y = 1 / x;
        // ... some more code using x
    }
private:
    Eigen::Matrix3d M;
};

假设

  • 与在函数范围内不断引用 M(1,1) 相比,使用单个引用 x 将产生更少的循环。
  • 潜在优化仅在 critical_function 内部产生影响,但不会在外部范围内延续,例如多次调用函数的循环。

编辑

类型已更正为 double(从 int 或 float),以与 Matrix3d 保持一致。

【问题讨论】:

  • 您很可能看不到任何差异,因为 M(1, 1) 无论如何都会在缓存中。 假设:您试图在不知道代码在哪里花费最多时间的情况下进行过早优化。
  • 换句话说:您的问题没有 100% 通用的答案。您将总是必须分析自己的代码,以找出最适合您的情况的代码。在您的情况下,它可能根本没有区别,因为编译器无论如何都会在幕后转换事物。这里有一个提示:一个部门花费的 CPU 时间比您现在担心的要多一到两个数量级。
  • 感谢两位的回答。确实,缓存会产生积极的影响,但是,在缓存中访问系数仍然会产生比访问缓存中的标量(例如浮点数)更多的周期?或不?我同意分析是正确的方法,是的,也许我很挑剔并且有更多有影响力的操作,我只是想很好地理解这些概念,以便我可以使用最佳实践。正如我所提到的,critical_function 被多次调用。您能否参考我所说的假设并确认或纠正我的理解?
  • 在选项 2 中,x 是否有意使用 int 而不是 double?选项 3 不太可能有所作为,因为 M(1,1) 将在编译时简化为等同于 ((double*)(this))[4]
  • 不,这是一个错误,感谢您指出这一点。我已将代码更正为始终加倍。使用x(选项2)的操作是否比访问((double*)(this))[4](选项1)更快?

标签: c++ optimization compiler-optimization eigen3


【解决方案1】:

总之,别费心写M(1,1)

如果您正在处理像Matrix3d 这样的编译时矩阵和编译时已知的索引,那么M(1,1) 中涉及的索引计算将被任何编译器完全优化掉。也就是说,下面两个sn-ps会生成同一个程序集:

struct A {
  Matrix3d M;
  void foo() { double x = M(1,1); }
};

struct A {
  double a, b, c, d, e, f, g, h, i;
  void foo() { double x = e; }
};

所以选项 2 会更糟,选项 3 也可能因为引入指针而降低性能。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-03-10
    • 1970-01-01
    • 2019-10-23
    • 2011-06-09
    • 1970-01-01
    • 1970-01-01
    • 2014-10-31
    相关资源
    最近更新 更多