英文原文(Explanation of the assertion on unaligned arrays)
该节将解释程序因断言失败而终止,如下所示:
my_program: path/to/eigen/Eigen/src/Core/DenseStorage.h:44:
Eigen::internal::matrix_array<T, Size, MatrixOptions, Align>::internal::matrix_array()
[with T = double, int Size = 2, int MatrixOptions = 2, bool Align = true]:
Assertion `(reinterpret_cast<size_t>(array) & (sizemask)) == 0 && "this assertion
is explained here: http://eigen.tuxfamily.org/dox-devel/group__TopicUnalignedArrayAssert.html
READ THIS WEB PAGE !!! ****"' failed.
此问题有 4 个已知原因。如果只能使用最新的编译器(例如,GCC>=7、clang>=5、MSVC>=19.12)来定位 [c++17],那么启用 c++17 就足够了(如果行不通,可以在 这里 给Eigen官方提Bug)。另外,请继续阅读以了解这些问题,以及如何解决它们。
如何在自己的代码中查找原因
首先,需要找出自己的代码中触发此断言的位置,乍一看,错误消息看起来没什么用,因为它指向的是 Eigen 中的一个文件。然而,由于程序崩溃了,如果可以复现崩溃,则可以使用任何调试器进行回溯。例如,如果使用的是 GCC
,则可以按如下方式使用 GDB 调试器:
$ gdb ./my_program # Start GDB on your program
> run # Start running your program
... # Now reproduce the crash!
> bt # Obtain the backtrace
现在已经准确地知道问题发生在自己代码中的什么位置,请继续阅读以了解需要更改的内容。
原因 1:包含Eigen对象的结构体
如果有类似如下代码:
class Foo
{
//...
Eigen::Vector4d v;
//...
};
//...
Foo *foo = new Foo;
那么需要阅读这篇文章:包含Eigen对象的结构体。
请注意,此处 Eigen::Vector4d
仅用作示例,更一般的是,所有 固定大小的可向量化Eigen对象 都会出现此问题。
原因 2:STL 容器或手动内存分配
如果将 STL 容器(例如 std::vector、std::map、...)与 Eigen 对象或包含 Eigen 对象的类一起使用,就像这样:
std::vector<Eigen::Matrix2d> my_vector;
struct my_class {
... Eigen::Matrix2d m; ... };
std::map<int, my_class> my_map;
那么需要阅读这篇文章:将STL容器与Eigen一起使用。
请注意,此处 Eigen::Matrix2d
仅用作示例,更一般地,所有 固定大小的可向量化Eigen对象 和 包含Eigen对象的结构体 都会出现此问题。
任何绕过 operator new
来分配内存的类/函数都会出现同样的问题,即先执行自定义内存分配再调用 placement new
运算符。例如,std::make_shared
或 std::allocate_shared
的情况,通常是使用 aligned allocator ,如 将STL容器与Eigen一起使用 中所述。
原因 3:按值传递 Eigen 对象
如果代码中的某个函数正在获取按值传递的 Eigen 对象,就像这样:
void func(Eigen::Vector4d v);
那么需要阅读这篇文章:按值将Eigen对象传递给函数。
请注意,此处 Eigen::Vector4d
仅用作示例,更一般的,所有 固定大小的可向量化 Eigen
对象 都会出现此问题。
原因 4:编译器对堆栈对齐做出了错误的假设(例如 Windows 上的 GCC)
对于在 Windows 上使用 GCC(如 MinGW 或 TDM-GCC)的人来说,这是必读的。如果在声明局部变量的函数中遇到此断言失败,如下所示:
void foo()
{
Eigen::Quaternionf q;
//...
}
那么需要阅读这篇文章:编译器对堆栈对齐做出了错误的假设。
请注意,此处 Eigen::Quaternionf
仅用作示例,更一般的,所有 固定大小的可向量化 Eigen
对象 都会出现此问题。
这个断言的一般解释
固定大小的可向量化 Eigen
对象 必须在正确对齐的位置创建,否则寻址它们的 SIMD 指令将崩溃。例如,SSE/NEON/MSA/Altivec/VSX
目标需要 16 字节对齐,而 AVX
和 AVX512
目标可能分别需要多达 32
字节和 64
字节对齐。
Eigen 通常会自动处理这些对齐问题,即为它们设置对齐属性并重载它们的 operator new
。
然而,在一些极端情况下,这些对齐设置会被覆盖:这是是导致此断言的可能原因。
如果不关心最佳矢量化,该如何处理?
有3种可能性:
- 对
Matrix
、Array
、Quaternion
等对象使用DontAlign
选项。这样 Eigen 就不会试图过度对齐它们,也不会假定任何特殊的对齐方式。不利的一面是,未对齐会增加加载/存储的成本。 - 如果将
EIGEN_MAX_STATIC_ALIGN_BYTES
定义为 0,会禁用所有 16 字节(及以上)静态对齐,同时保持 16 字节(或以上)堆对齐。通过未对齐存储(由EIGEN_UNALIGNED_VECTORIZE
控制)影响固定大小对象(如 Matrix4d)的矢量化效果,同时保持动态大小对象(如 MatrixXd)的矢量化不变。在 64 字节系统上,还可以将EIGEN_MAX_STATIC_ALIGN_BYTES
定义为16
以只禁用 32 和 64 字节的过度对齐。但请注意,这会破坏 ABI 与默认静态对齐行为的兼容性。 - 或者同时定义
EIGEN_DONT_VECTORIZE
和EIGEN_DISABLE_UNALIGNED_ARRAY_ASSERT
。这保留了 16 字节(或以上)对齐,从而保留了 ABI 兼容性,但完全禁用了矢量化。
为什么定义 EIGEN_DONT_VECTORIZE
本身不会禁用 16 字节(或以上)对齐和断言,解释如下:
它不会禁用断言,因为在没有矢量化的情况下运行良好的代码会在启用矢量化时突然崩溃。它不会禁用 16 字节(或以上)对齐,因为这意味着矢量化和非矢量化代码不兼容。这种 ABI 兼容性非常重要,即使对于只开发内部应用程序的人来说也是如此,例如,可能希望在同一应用程序中拥有一个矢量化路径和一个非矢量化路径。
如何检查代码在对齐问题上是否安全?
在 c++ 中不可能在编译时检测出上述任何问题(尽管静态分析器变得越来越强大并且可以检测到其中的一些问题)。即使在运行时,我们所能做的就是捕获无效的未对齐分配并触发本节开头提到的显式断言。因此,虽然程序在具有某些给定编译标志的给定系统上运行良好,但并不能保证代码是安全的。例如,在大多数 64 位系统上,缓冲区在 16 字节边界上对齐,如果不启用 AVX 指令集,那么代码将运行良好。如果移植到其他平台,或者启用默认需要 32 字节对齐的 AVX 指令,则相同的代码可能会出现断言。
不过,并非毫无希望。假设代码已被单元测试很好地覆盖,那么可以通过将其链接到仅返回 8 字节对齐缓冲区的自定义 malloc 库来检查对齐安全性。这样所有对齐的问题都会暴漏。为此,还必须使用 EIGEN_MALLOC_ALREADY_ALIGNED=0
编译程序。