Skip to content

Latest commit

 

History

History
160 lines (140 loc) · 5.61 KB

StructHavingEigenMembers.md

File metadata and controls

160 lines (140 loc) · 5.61 KB

包含Eigen对象作为成员的结构


总结

如果你在你的结构里定义了固定大小可量化类型的成员变量,那么你必须重载它的new操作符来保证指向对象的指针是位于16字节整数倍的位置上。好消息是,Eigen提供了一个宏EIGEN_MAKE_ALIGNED_OPERATOR_NEW来帮助你完成这个任务。

什么样的代码需要修改?


像下面这样的代码需要修改:

class Foo
{
  ...
  Eigen::Vector2d v;
  ...
};
...
Foo *foo = new Foo;

文字描述就是:你的类的成员变量包含有固定大小可量化Eigen对象,同时你还在动态生成这个类的一个实例。

这种代码要如何修改?

非常简单,你只需要在你类的public域添加宏EIGEN_MAKE_ALIGNED_OPERATOR_NEW就可以了,就像下面这样:

class Foo
{
  ...
  Eigen::Vector2d v;
  ...
public:
  EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
...
Foo *foo = new Foo;

这个宏会让"new Foo"返回一个内存对齐的指针。 如果你非常讨厌使用这种方式,那么你可以参考其他方式

为什么必须这么做?

好的,假设你的代码是下面这样:

class Foo
{
  ...
  Eigen::Vector2d v;
  ...
};
...
Foo *foo = new Foo;

一个Eigen::Vector2d包含两个double数据,总共是128位。这个大小正好是一个SSE数据包的大小,针对这个vector的大多数操作都可以都可以使用SSE。但是SSE指令(至少Eigen使用的,速度最快的那一种)要求数据指针是128位对齐的。不然的话,程序将出现段错误。 因此,Eigen使用了下面两种手段自己处理了Eigen::Vector2d的对齐任务:

  • 对于Eigen::Vector2d的数组(两个double),Eigen要求它们满足128位对齐。在GCC编译器下,这个是通过attribute((aligend(16)))实现的。
  • Eigen重载了Eigen::Vector2d的new操作符来保证得到的指针满足128位对齐。

所以,你无需担心什么,Eigen已经帮你处理好了对齐任务... ...除了一种例外。如果你的代码是上面的Foo那种,然后,你像例子那样动态生成了一个对象。因为Foo类没有内存对齐的new操作符,返回的指向对象的指针foo并不能保证是128位对齐的。 属性对象v的对齐与否完全依赖与类对象的起始地址foo.如果指针foo不是对齐的,那么foo->v当然也不会是对齐的! 解决的办法是,用上一节讲的方法,给类Foo提供一个对齐的new操作符。

我因该将Eigen类型的成员都放在类定义的开头吗?

并没有这种强制要求。因为声明了128位对齐后Eigen会自己处理这个任务,如果有的成员在进行128位对齐的时候有这种需求,它们会根据类的具体情况自动调整分布的位置。代码像下面这样写就可以了:

class Foo
{
  double x;
  Eigen::Vector2d v;
public:
  EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};

那么动态大小的矩阵和向量怎么处理?

Eigen::VectorXd这样的动态矩阵和向量会自动分配自己成员的地址,所以,它们对齐的要求是自动完成的。它们并不会引起文章提到的问题。我们讨论的问题只会发生在固定大小可量化的矩阵和向量身上。

所以这其实是Eigen的一个bug?

不,这不是我们的bug。这更多的是从C++98语言特性中继承过来的问题,而且好像在后来的版本中已经得到修复了,详情请参看这份文档

我能否有选择的执行对齐(根据模板参数)?

为了满足这种需求,我们提供了这样一个宏EIGEN_MAKE_ALIGNED_OPERATOR_NEW_IF(NeedsToAlign)。当NeedToAlign为真的时候,它跟EIGEN_MAKE_ALIGNED_OPERATOR_NEW 的效果是一样的。如果NeedToAlign为假,它将使用默认分配器的对齐方式。 示例:

template<int n> class Foo
{
  typedef Eigen::Matrix<float,n,1> Vector;
  enum { NeedsToAlign = (sizeof(Vector)%16)==0 };
  ...
  Vector v;
  ...
public:
  EIGEN_MAKE_ALIGNED_OPERATOR_NEW_IF(NeedsToAlign)
};
...
Foo<4> *foo4 = new Foo<4>; // foo4 is guaranteed to be 128bit-aligned
Foo<3> *foo3 = new Foo<3>; // foo3 has only the system default alignment guarantee

其他问题

需要在每个类里面添加宏EIGEN_MAKE_ALIGNED_OPERATOR_NEW确实太麻烦了,所以你还有至少两种其他解决方案。

禁用对齐


第一种方法是禁用固定大小成员的对齐要求:

class Foo
{
  ...
  Eigen::Matrix<double,2,1,Eigen::DontAlign> v;
  ...
};

这会导致使用v的时候无法使用量化。如果Foo里有函数多次访问这个成员,有可能v会被拷贝到一个对齐的临时vector中然后自动开启量化:

void Foo::bar()
{
  Eigen::Vector2d av(v);
  // use av instead of v
  ...
  // if av changed, then do:
  v = av;
}

私有结构


第二个解决方案是,将固定大小的对象放到私有结构体里,这样,Eigen对象会伴随类对象一起动态生成:

struct Foo_d
{
  EIGEN_MAKE_ALIGNED_OPERATOR_NEW
  Vector2d v;
  ...
};
struct Foo {
  Foo() { init_d(); }
  ~Foo() { delete d; }
  void bar()
  {
    // use d->v instead of v
    ...
  }
private:
  void init_d() { d = new Foo_d; }
  Foo_d* d;
};

这么做可见的好处是,Foo类不再需要为了解决对齐问题而刻意修改。但不足在于,你需要在堆分配的时候进行其他处理。