[Eigen中文文档] 对未对齐数组断言的解释

简介: 本文将解释程序因断言失败而终止的问题。

文档总目录

英文原文(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_sharedstd::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 字节对齐,而 AVXAVX512 目标可能分别需要多达 32 字节和 64 字节对齐。

Eigen 通常会自动处理这些对齐问题,即为它们设置对齐属性并重载它们的 operator new

然而,在一些极端情况下,这些对齐设置会被覆盖:这是是导致此断言的可能原因。

如果不关心最佳矢量化,该如何处理?

有3种可能性:

  • MatrixArrayQuaternion等对象使用 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_VECTORIZEEIGEN_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 编译程序。

相关文章
|
编译器 C语言 Windows
[Eigen中文文档] 编译器对堆栈对齐做出了错误的假设
本文将介绍编译器对堆栈对齐做出了错误的假设问题。
122 0
[Eigen中文文档] 编写以特征类型为参数的函数(一)
Eigen使用表达式模板的方式导致每个表达式的类型可能都不同。如果将这样的表达式传递给一个需要Matrix类型参数的函数,则表达式将隐式地被评估为一个临时Matrix,然后再传递给函数。这意味着失去了表达式模板的好处。
144 0
|
数据采集 Python 数据可视化
[Python] 数据预处理(缺失值、异常值、重复值) [相关方法参数说明、代码示例、相关概念](三)
[Python] 数据预处理(缺失值、异常值、重复值) [相关方法参数说明、代码示例、相关概念](三)
|
数据采集 Python
[Python] 数据预处理(缺失值、异常值、重复值) [相关方法参数说明、代码示例、相关概念](二)
[Python] 数据预处理(缺失值、异常值、重复值) [相关方法参数说明、代码示例、相关概念](二)
数组的解释及使用方法
数组的解释及使用方法
84 0
|
C语言
C语言:写一个函数返回参数二进制中 1 的个数(三种思路)-1
思路一:使用 %2 和 /2 取出每一位并判断 总体思路: (一). 创建函数,参数要设置成无符号整数,设置计数器计算1的个数 (二). 使用 while循环 循环判断二进制每一位, 使用 %2 判断最低位是否为 1, 使用 /2 去掉判断了的最低位,下次循环开始判断新的最低位
 C语言:写一个函数返回参数二进制中 1 的个数(三种思路)-1
|
存储 编译器
[Eigen中文文档] 编写以特征类型为参数的函数(二)
Eigen使用表达式模板的方式导致每个表达式的类型可能都不同。如果将这样的表达式传递给一个需要Matrix类型参数的函数,则表达式将隐式地被评估为一个临时Matrix,然后再传递给函数。这意味着失去了表达式模板的好处。
86 0
|
Python
Python的知识点运用-2(排序&&找差值及修正ts合成顺序)
Python的知识点运用-2(排序&&找差值及修正ts合成顺序)
56 0
|
数据采集 Python
[Python] 数据预处理(缺失值、异常值、重复值) [相关方法参数说明、代码示例、相关概念](一)
[Python] 数据预处理(缺失值、异常值、重复值) [相关方法参数说明、代码示例、相关概念]
|
Python
Python经典编程习题100例:第44例:两个 3 行 3 列的矩阵,实现其对应位置的数据相加,并返回一个新矩阵
Python经典编程习题100例:第44例:两个 3 行 3 列的矩阵,实现其对应位置的数据相加,并返回一个新矩阵
297 0