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

相关文章
|
编译器 索引
[Eigen中文文档] 块操作
本文介绍了块操作。块是matrix或array的部分矩形元素。块表达式既可以用作右值也可以用作左值。与Eigen表达式一样,如果让编译器进行优化,则块操作的运行时间成本为零。
490 0
|
存储 算法 NoSQL
[Eigen中文文档] 存储顺序
矩阵和二维数组有两种不同的存储顺序:列优先和行优先。本节解释了这些存储顺序以及如何指定应该使用哪一种。
697 0
|
编译器 C++ 容器
[Eigen中文文档] 将STL容器与Eigen一起使用
如果使用足够新的编译器(例如,GCC&gt;=7、clang&gt;=5、MSVC&gt;=19.12)以 [c++17] 模式编译,那么编译器会自动处理所有事情,可以跳过本节。 否则,在固定大小的可向量化 Eigen 类型或具有此类成员的类上使用 STL 容器时,需要使用过度对齐的分配器。
549 0
|
人工智能 缓存 Ubuntu
【Ubuntu】Ubuntu安装PCL(安装PCL/卸载PCL/查看PCL版本/PCL报错处理相关操作)(史上最详细)
【Ubuntu】Ubuntu安装PCL(安装PCL/卸载PCL/查看PCL版本/PCL报错处理相关操作)(史上最详细)
|
安全 编译器 C++
[Eigen中文文档] 矩阵与向量运算
本文章旨在提供有关如何使用 Eigen 在矩阵、向量和标量之间执行算术操作的概述和一些详细信息。
1063 0
|
存储 编译器 对象存储
[Eigen中文文档] 包含Eigen对象的结构体
如果定义的结构体包含固定大小的可向量化 Eigen 类型成员,则必须确保对其调用 operator new 来分配正确的对齐缓冲区。如果仅使用足够新的编译器(例如,GCC>=7、clang>=5、MSVC>=19.12)以 [c++17] 模式编译,那么编译器会自动处理所有事情,可以跳过本节。 否则,必须重载它的 operator new 以便它生成正确对齐的指针(例如,Vector4d 和 AVX 的 32 字节对齐)。幸运的是,Eigen 为提供了一个宏 EIGEN_MAKE_ALIGNED_OPERATOR_NEW 来完成这项工作。
572 0
|
存储 NoSQL API
[Eigen中文文档] Matrix类
在Eigen中,所有矩阵和向量都是Matrix模板类的对象。向量只是行数或者列数为1的特殊矩阵。
1045 1
|
测试技术 API C++
[Eigen中文文档] 扩展/自定义Eigen(一)
在本节中,将介绍如何向MatrixBase添加自定义方法。由于所有表达式和矩阵类型都继承自MatrixBase,因此将方法添加到MatrixBase会立即使其对所有表达式可用!一个典型的用例是,使Eigen与另一个API兼容。
816 0
|
存储 并行计算 算法
[Eigen中文文档] 概述(总目录)
Eigen是基于线性代数的C ++模板库,主要用于矩阵,向量,数值求解器和相关算法。常用的Ceres、G2O等项目均是基于Eigen库。 本系列文章将通过官方文档带你了解Eigen。
3257 1
|
存储 C++
[Eigen中文文档] 原始缓冲区接口:Map 类
本节解释了如何使用“原始”C/C++ 数组。这在各种情况下都很有用,特别是在将向量和矩阵从其他库“导入”到 Eigen 中时。
508 0