-
Notifications
You must be signed in to change notification settings - Fork 54
Eigen Memory Issues
Eigen is a highly efficient linear algebra library for C++.
Eigen's focus on efficient operation has two main downsides
- When compiled in release mode it will blindly assume that all of the memory needed for any operation has been correctly allocated in the right location, without any form of checks. It will also not check that any inputs meet the required prerequisites (for example that matrices are of the correct size for multiplication to be a meaningful operation).
- To allow it to use SIMD instructions it assumes all fixed size Eigen types have been allocated at 16-byte-aligned location.
These two downsides combine to give a large range of subtle hard to track down bugs.
- Never pass an eigen-type by value.
- Always use Eigen::aligned_allocator when using a fixed size eigen-type with a std::container.
- Use EIGEN_MAKE_ALIGNED_OPERATOR_NEW on classes/structs with fixed size eigen-type elements.
- Be very careful when assigning to memory you are operating on.
- Always check the matrices are the right size and their memory is correctly allocated.
- Don't mix Eigen versions
Chances are you are here because someone saw that you had one of the issues mentioned below. So if everything is working, why should you care?
-
Chances are your code is currently very fragile: Issues with alignment can be caused or solved simply by changing the order variables are declared in. Similarity issues with Eigen/StdVector can be caused by the ordering of includes. This means even the smallest cosmetic change can stop your code building or make it start segfaulting.
-
There is a good chance that your code won't work on a subtly different system: Many alignment issues that do not cause problems on Ubuntu 14.04, immediately segfault things in Ubuntu 15.04.
-
Your code might be broken in a way that has so far gone unnoticed: For example, when two mismatched Eigen versions were used with manifold mapping the only symptom was that a Boolean initialized to true would change its value to false.
- Most likely issue: memory misalignment
- Possible issues: mismatched eigen versions
- Possible issues: mixed use of StdVector
- Most likely issue: memory misalignment
- Possible issues: manual memory assignment, incorrectly sized matrices, mismatched Eigen versions or mismatched build flags
- Most likely issue: incorrectly sized matrices or destructive eigen operations
- Possible issues: everything
- Issue: mixed use of StdVector
There are issues in non-Eigen elements / the program appears to defy the core principles of the C++ language:
This covers everything from a non-Eigen variable changing its value between being read and written, to multiplying two variables causing an infinite loop. The behavior is usually highly dependent on the exact system.
- Most likely issue: mismatched Eigen versions
- Possible issues: memory misalignment or mismatched build flags
See here for the official Eigen docs on this issue
This is the most common issue with Eigen. As a quick check for this issue try disabling all Eigen vectorization (add #define EIGEN_DONT_VECTORIZE
and #define EIGEN_DISABLE_UNALIGNED_ARRAY_ASSERT
) and recompile.
If this fixes your problem, this was your issue. However, disabling vectorization will tank your codes efficiency so read on to find out how to fix it correctly.
Note: This issue appears very OS dependent, with OSX seeming the most robust to it and Ubuntu 16.04 the least. It will usually cause a segfault if encountered. The following changes are only actually required if you are using fixed Eigen types that have a number of bytes divisible by 16, however it doesn't hurt and it is easier just to do it everywhere an Eigen type shows up.
-
std::vector<T>
->std::vector<T, Eigen::aligned_allocator<T>>
-
std::map<A, B>
->std::map<std::pair<A,B>, Eigen::aligned_allocator<std::pair<A,B>>>
-
std::queue<T>
->std::queue<T, std::deque<T, Eigen::aligned_allocator<T>>
- etc...
Change to passing by reference and then make an internal copy if you need to modify things
void func(Eigen::Vector3f vec){ ...
-> void func(const Eigen::Vector3f& vec_in){ Eigen::Vector3f vec = vec_in; ...
If you have a class or struct with a raw Eigen type as a variable (containers holding Eigen types don't count) you must add the EIGEN_MAKE_ALIGNED_OPERATOR_NEW macro in the public part.
struct Foo{ Eigen::Vector3f vecA; Eigen::Vector3f vecB};
-> struct Foo{EIGEN_MAKE_ALIGNED_OPERATOR_NEW Eigen::Vector3f vecA; Eigen::Vector3f vecB}
;
The use of a combination of the system Eigen and eigen_catkin means it is possible for a program to be built against one version of Eigen and then linked with another. If this happens there is no limit to the number or nature of strange issues that can appear.
Before C++11 how the memory alignment of std::vector
was handled meant that it could not be used with Eigen types. To get around this Eigen defined its own std::vector
in Eigen/StdVector
. Since C++11 this is no longer needed and so most libraries do not use it.
However, this causes an error complaining about the partial specialization of std::vector
if std::vector
has been used to hold an Eigen type before Eigen/StdVector
is included. This issue is only present in Eigen versions before 3.3.
To solve it there are several options:
- upgrade to a newer Eigen
- add
#include<Eigen/StdVector>
before anystd::vector
is used (including inside other includes) - remove
#include<Eigen/StdVector>
everywhere you find it (many libraries such as pcl use it internally, so this may not be possible) - patch Eigen
When compiled in release mode Eigen will happily perform such operations as
Eigen::MatrixXd mat_a(Eigen::MatrixXd::Random(100,100));
Eigen::MatrixXd mat_b(Eigen::MatrixXd::Random(5,5));
mat_a = mat_a * mat_b;
Clearly the matrix sizes make this operation impossible and it is not clear what Eigen is actually doing, though for some matrix sizes this does not cause a segfault or any warnings / errors. This sort of thing can be difficult to spot when one matrix is a transpose of what it is meant to be.
Some operations can cause Eigen to reallocate a matrix's memory, potentially changing the values of the matrix. ::resize is an example of such a function.
Sometimes this issue can be subtle for example:
Eigen::MatrixXf mat(Eigen::MatrixXf::Random(100,100)); mat = mat.block(0,0,50,50);
In the above example some of the elements of mat
may obtain erroneous values as the operation overwrites some values before it reads from them.
Behind the scenes Eigen stores all the matrix data in what is basically a raw c array. While this is usually handled internally, it is possible to access and manipulate this data directly. You can even create your own memory which an Eigen matrix can then use. However, as Eigen performs no checks that this memory is valid this can cause your program to segfault or have other ill defined behavior if the memory has not been allocated correctly.
As an example of this, the graph optimization software g2o uses Eigen matrices to store the hessian of each vertex. However, while the hessians are created with the vertex, the matrix's internal data pointer is set to NULL until after the first optimization. This means that while operations such as .cols()
are valid, attempting to access an element will cause a segfault.
Eigen supports different kinds of vectorization instructions to speed up computations. Different vectorization instructions expect different alignment: e.g. SSE instructions work on 16-byte-aligned data whereas AVX instructions (supported in Eigen >=3.3) expects 32-byte-aligned data. These instructions are enabled with compiler flags named -m*
: -msse
would enable SSE instructions for the current program and -mavx
enables AVX instructions. With -march=native
, all instructions that are supported by your CPU are enabled.
Linking programs together with different vectorization instructions enabled can lead to all kinds of weird segfaults. These segfaults are commonly caused by Eigen expecting a different alignment and using the wrong vectorization functions.
Example: You're using Eigen 3.3 package is compiled with only -msse
and linked against a package that was compiled with -msse -mavx
. Now somewhere in your code, the program may segfault because it tries to use AVX instructions (which need 32-byte-aligned data) on your 16-byte-aligned matrices.
To fix these issues, make sure that all your packages (including third-party libraries) are compiled with the same flags. Especially pay attention to the flags related to these vectorization instructions -m*
or -march
.