[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 编译程序。

相关文章
|
8月前
|
编译器 索引
[Eigen中文文档] 块操作
本文介绍了块操作。块是matrix或array的部分矩形元素。块表达式既可以用作右值也可以用作左值。与Eigen表达式一样,如果让编译器进行优化,则块操作的运行时间成本为零。
64 0
|
2月前
|
算法 编译器 C语言
【C++ 函数 基本教程 第六篇 】深度解析C++函数符号:GCC与VS的名称修饰揭秘
【C++ 函数 基本教程 第六篇 】深度解析C++函数符号:GCC与VS的名称修饰揭秘
49 1
|
2月前
|
JavaScript 前端开发
js开发:请解释什么是ES6的解构赋值(destructuring assignment),并给出一个示例。
ES6的解构赋值简化了JavaScript中从数组和对象提取数据的过程。例如,`[a, b, c] = [1, 2, 3]`将数组元素赋值给变量,`{name, age} = {name: &#39;张三&#39;, age: 18}`则将对象属性赋值给对应变量,提高了代码的可读性和效率。
|
8月前
|
编译器 C语言 Windows
[Eigen中文文档] 编译器对堆栈对齐做出了错误的假设
本文将介绍编译器对堆栈对齐做出了错误的假设问题。
65 0
|
8月前
[Eigen中文文档] 编写以特征类型为参数的函数(一)
Eigen使用表达式模板的方式导致每个表达式的类型可能都不同。如果将这样的表达式传递给一个需要Matrix类型参数的函数,则表达式将隐式地被评估为一个临时Matrix,然后再传递给函数。这意味着失去了表达式模板的好处。
80 0
|
8月前
数组的解释及使用方法
数组的解释及使用方法
59 0
|
8月前
|
存储 编译器
[Eigen中文文档] 编写以特征类型为参数的函数(二)
Eigen使用表达式模板的方式导致每个表达式的类型可能都不同。如果将这样的表达式传递给一个需要Matrix类型参数的函数,则表达式将隐式地被评估为一个临时Matrix,然后再传递给函数。这意味着失去了表达式模板的好处。
55 0
|
9月前
|
移动开发 小程序 JavaScript
uniapp进行条件编译的两种方法?小程序端和H5的代表值是什么
在 UniApp 中,可以使用条件编译来根据不同的平台(小程序、H5 等)进行不同的代码处理。有两种主要的方法来实现条件编译:使用 mp 属性和条件注释。
|
9月前
|
XML 测试技术 程序员
预处理——参考《C和指针》第14章
预处理——参考《C和指针》第14章
88 0
|
JavaScript 网络架构
ES6知识点补充——剩余参数、展开语法
JS查漏补缺系列是我在学习JS高级语法时做的笔记,通过实践费曼学习法进一步加深自己对其的理解,也希望别人能通过我的笔记能学习到相关的知识点。这一次我们来了解剩余参数、展开语法
155 0