【Eigen】Eigen内存对齐超详细讲解

Eigen是一个非常常用的矩阵运算库,由于使用了SSE指令集进行向量化加速,因此它的矩阵运算能力还是很厉害的,在SLAM等领域是一个不可或缺的的工具。然而,有时候在vector容器或者class类中使用Eigen时,可能会出现一些奇奇怪怪的错误。

究其原因,可能是Eigen对象没有进行内存对齐,从而导致程序崩溃

本文着重于Eigen什么情况下需要内存对齐?用户如何实现内存对齐的功能。而如果想要一步步看其演变过程,有一篇文章写的很好:从Eigen向量化谈内存对齐。


为什么要内存对齐

Eigen矩阵运算快的主要原因之一就是向量化运算。

所谓向量化运算,就是利用SSE、AVX等SIMD(Single Instruction Multiple Data)指令集,实现一条指令对多个操作数的运算,从而提高代码的吞吐量,实现加速效果。SSE是一个系列,包括从最初的SSE到最新的SSE4.2,支持同时操作16 bytes的数据,即4个float或者2个double。AVX也是一个系列,它是SSE的升级版,支持同时操作32 bytes的数据,即8个float或者4个double。

向量化运算是有前提的,那就是内存对齐。SSE的操作数,必须16 bytes对齐,而AVX的操作数,必须32 bytes对齐。也就是说,如果我们有4个float数,必须把它们放在连续的且首地址为16的倍数的内存空间中,才能调用SSE的指令进行运算。

因此,对于Eigen对象来说,一旦需要使用SSE指令集进行向量化运算,那必然要求它从内存地址16 byte整数倍的地方开始。如果不满足这个条件,在程序的运行过程中,一旦开始使用SSE指令集,就会产生错误,甚至崩溃

Eigen对象都需要内存对齐么?

Eigen类型一般可以分为两类:固定大小、非固定大小。

能够向量化运算的固定大小的Eigen类,也被称为固定大小向量化Eigen类型(fixed-size vectorizable Eigen types)。这要求Eigen对象:具有固定大小并且该大小是16个字节的倍数(即16字节对齐的)

例如:Eigen::Vector4d是16字节的倍数,但Eigen::Vector3d不是,它是8字节的倍数。下面把一些常见的Eigen类型对齐大小做了打印:

std::cout << alignof(Eigen::Vector2d) << std::endl;     // 16
std::cout << alignof(Eigen::Vector3d) << std::endl;     // 8
std::cout << alignof(Eigen::Vector4d) << std::endl;     // 16
std::cout << alignof(Eigen::Vector2f) << std::endl;     // 4
std::cout << alignof(Eigen::Vector3f) << std::endl;     // 4
std::cout << alignof(Eigen::Vector4f) << std::endl;     // 16
std::cout << alignof(Eigen::Matrix2d) << std::endl;     // 16
std::cout << alignof(Eigen::Matrix3d) << std::endl;     // 8
std::cout << alignof(Eigen::Matrix2f) << std::endl;     // 16
std::cout << alignof(Eigen::Matrix3f) << std::endl;     // 4
std::cout << alignof(Eigen::Matrix4d) << std::endl;     // 16
std::cout << alignof(Eigen::Matrix4f) << std::endl;     // 16
std::cout << alignof(Eigen::Affine3d) << std::endl;     // 16
std::cout << alignof(Eigen::Affine3f) << std::endl;     // 16
std::cout << alignof(Eigen::Quaterniond) << std::endl;  // 16
std::cout << alignof(Eigen::Quaternionf) << std::endl;  // 16

如果一个Eigen对象,它不是固定大小向量化Eigen类型,那么它就不会进行向量化操作,更不用说会有什么内存对齐的问题了

非固定大小的Eigen对象,例如MatrixXf。动态大小的Eigen对象的内存由它自己管理和释放,也就是说它的内存申请和释放是Eigen已经重构为对齐的malloc,因此不需要用户额外指定字节对齐

这里看来只有某些固定大小向量化Eigen类型才需要用户额外指定内存对齐,这也正是我们需要关注的。


栈上Eigen

对于局部变量(栈内存),它们的内存地址是在编译期确定的,也就是由编译器决定。所以通过预编译指令来实现栈空间的对齐对齐。不同编译器的预编译指令是不一样的,比如gcc的语法为__attribute__((aligned(16))),MSVC的语法为__declspec(align(16))。当然,c++ 11提供了关键字alignas来告诉编译器按照指定字节进行对齐,使用起来更方便。例如:

// sse_t类型的每个对象将对齐到16字节边界
struct alignas(16) sse_t
{float sse_data[4];
};// 数组cacheline将对齐到128字节边界
alignas(128) char cacheline[128];

同理,栈上Eigen也可以通过alignas来进行内存对齐的,具体的实现在Eigen/src/Core/DenseStorage.h中,固定大小Eigen对象中的数据结构是plain_array类型,通过模板来设置不同的内存对齐大小。

template <typename T, int Size, int MatrixOrArrayOptions,int Alignment = (MatrixOrArrayOptions&DontAlign) ? 0: compute_default_alignment<T,Size>::value >
struct plain_array
{T array[Size];// ...
};template <typename T, int Size, int MatrixOrArrayOptions>
struct plain_array<T, Size, MatrixOrArrayOptions, 16>
{EIGEN_ALIGN_TO_BOUNDARY(8) T array[Size];// ...
};template <typename T, int Size, int MatrixOrArrayOptions>
struct plain_array<T, Size, MatrixOrArrayOptions, 16>
{EIGEN_ALIGN_TO_BOUNDARY(16) T array[Size];// ...
};

其中,EIGEN_ALIGN_TO_BOUNDARY宏,就是调用了alignas来指定8字节对齐。其定义为:

#define EIGEN_ALIGN_TO_BOUNDARY(n) alignas(n)

可以看到plain_array类型在定义的时候,就已经会根据它是否需要内存对齐进行alignas对齐了。因此,栈上空间不需要用户额外进行内存对齐操作


堆上Eigen

栈内存,它是由编译器在编译时确定,因此预编译指令会生效。但用堆内存,它的内存地址是在运行时确定。C++的运行时库并不会关心预编译指令声明的对齐方式,因此关键字alignas对于堆上Eigen没有作用

STL容器

如果STL容器中的元素是Eigen数据结构时,其内存空间也是定义在堆上的。那么如何保证其的内存对齐呢?

由于c++11之前,STL并没有提供内存对齐的allocator方式,而Eigen自己的类型通过重构new和delete方法来实现了对齐的aligned_allocator,因此只需要在定义容器的时候指定Eigen实现的aligned_allocator即可

例如,用vector容器存储Eigen::Matrix4f类型或用map存储Eigen::Vector4f数据类型时,不过不做任何处理:

std::vector<Eigen::Matrix4d>
std::map<int, Eigen::Vector4f>

这么使用的话,编译能通过,当运行时会报段错误。

这个时候,如果使用Eigen自己定义的内存分配器,则可避免这个问题(需要include头文件):

#include <Eigen/StdVector>std::vector<Eigen::Vector4f, Eigen::aligned_allocator<Eigen::Vector4f> >
std::map<int, Eigen::Vector4f, std::less<int>,Eigen::aligned_allocator<std::pair<const int, Eigen::Vector4f> > >

注意:map的第三个参数std::less只是默认值,但是我们必须包含它,因为我们要指定第四个参数,即分配器类型。

其实,上述的这种才是标准的定义容器方法。STL容器的内存申请默认是std::allocator,并没有内存对齐,因此使用Eigen类型的STL容器的时候必须指定Eigen::aligned_allocator用于内存对齐

当然,还有一个方法,但可能不是那么常用,使用宏EIGEN_DEFINE_STL_VECTOR_SPECIALIZATION:

#include <Eigen/StdVector>EIGEN_DEFINE_STL_VECTOR_SPECIALIZATION(Eigen::Matrix2d)
std::vector<Eigen::Vector2d>

需要注意的是:必须在所有该Eigen对象出现前使用这个宏;并且该宏只专用于vector,如果是map,则不能如此使用。

类成员

如果用户自己创建的类型中包含Eigen类型,那么在进行new、delete操作时,也会在堆上空间进行对象创建。此时,则需要用户自己重写operator new和operator delete方法来保证内存的对齐

当然,为了简化用户的操作,Eigen提供了宏EIGEN_MAKE_ALIGNED_OPERATOR_NEW来帮助用户实现堆上申请的内存对齐(需要在public权限下)。例如:

class Foo {public:EIGEN_MAKE_ALIGNED_OPERATOR_NEWprivate:Eigen::Vector2d v;
};Foo *foo = new Foo();

此时,就会对自定义类型的new、delete方法进行重写了。通过源码,可以看到在Eigen/src/Core/util/Memory.h中通过宏重写了new和delete:

#define EIGEN_MAKE_ALIGNED_OPERATOR_NEW_IF(NeedsToAlign) \EIGEN_DEVICE_FUNC \void *operator new(std::size_t size) { \return Eigen::internal::conditional_aligned_malloc<NeedsToAlign>(size); \} \EIGEN_DEVICE_FUNC \void *operator new[](std::size_t size) { \return Eigen::internal::conditional_aligned_malloc<NeedsToAlign>(size); \} \EIGEN_DEVICE_FUNC \void operator delete(void * ptr) EIGEN_NO_THROW { Eigen::internal::conditional_aligned_free<NeedsToAlign>(ptr); } \EIGEN_DEVICE_FUNC \// ...

当然,还有其他的方案可以避免程序出错。例如,直接禁用内存对齐:

class Foo {Eigen::Matrix<double, 2, 1, Eigen::DontAlign> v;
};

它的作用是在使用v时禁用矢量化。但如果Foo的函数多次使用它,那么仍然可以通过将其复制到对齐的临时向量来重新启用矢量化:

void Foo::bar() {Eigen::Vector2d av(v);v = av;
}

按值传递Eigen对象

在C++中,按值传递对象几乎总是一个非常糟糕的做法,因为这意味着无用的副本,最好通过引用传递它们。

使用Eigen时,这甚至更重要:按值传递固定大小向量化Eigen类型,不仅效率低下,而且可能是非法的,或者会使程序崩溃!原因是这些Eigen对象具有对齐修饰符,当按值传递它们时,这些修饰符aren’t respected

因此,例如,像这样的函数:

void my_function(Eigen::Vector2d v);

需要重写如下,通过引用传递v:

void my_function(const Eigen::Vector2d& v);

总结

只有固定大小向量化Eigen类型(fixed-size vectorizable Eigen types)才有可能需要用户额外进行内存对齐的操作,如Vector3f、MatrixXd等,都不需要用户操心。

对于固定大小向量化Eigen类型,在下面两种情况需要用户进行额外内存对齐:

  • STL容器使用固定大小向量化Eigen类型对象成员
  • 自定义类含有固定大小向量化Eigen类型对象成员

基本思想就是:对于基本数据类型和自定义类型,我们需要用预编译指令来保证栈内存的对齐,用重写operator new的方式保证堆内存对齐。对于嵌套的自定义类型,申请栈内存时会自动保证其内部数据类型的对齐,而申请堆内存时仍然需要重写operator new。


相关阅读

  • 一文带你了解Eigen内存对齐
  • Eigen学习笔记13:固定大小的可矢量化Eigen对象
  • Eigen学习笔记14:具有Eigen成员的结构体
  • Eigen学习笔记15:Using STL Containers with Eigen
  • Eigen学习笔记16:按值传递Eigen对象

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1052.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

10个在线ai文案自动生成器,轻松写出高质量原创文案

在数字营销时代&#xff0c;撰写引人注目的标题和吸引人的营销文案至关重要。然而&#xff0c;写作优质文案需要耗费时间和精力。幸运的是&#xff0c;现在有许多在线AI文案自动生成器可以帮助我们快速创作出高质量的标题和营销文案。本文将介绍十款值得推荐的在线AI文案自动生…

微信小程序事件点击跳转页面的五种方法

第一种:标签 这是最常见的一种跳转方式,相当于html里的a标签 <navigator url"/pages/main/main"></navigator>第二种:wx.navigateTo({})方法 1.前端wxml <button bindtap"getCeshi" type"primary"> 测试按钮 </button>…

flink水位线传播及任务事件时间

背景 本文来讲解一下flink的水位线传播及对其对任务事件时间的影响 水位线 首先flink是通过从源头生成水位线记录的方式来实现水位线传播的&#xff0c;也就是说水位线是嵌入在正常的记录流中的特殊记录&#xff0c;携带者水位线的时间戳&#xff0c;以下我们就通过图片的方…

springCloud通过两种方式配置热更新

该热更新实际就是通过改动nacos官网里面的配置管理的妹纸内容实现 定义一个config包&#xff0c;在该包下面复制该代码 package cn.itcast.user.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.spring…

【C++】多线程编程二(std::thread详解)

目录 std::thread详解 &#xff08;1&#xff09;启动线程 ①无参无返回的函数作为入参 ②函数对象&#xff08;仿函数&#xff09;作为入参 &#xff08;2&#xff09;不等待线程detch() &#xff08;3&#xff09;等待线程完成join() &#xff08;4&#xff09;向线程…

力扣142. 环形链表 II

题目 给定一个链表的头节点head&#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回null。 链接&#xff1a;142. 环形链表 II - 力扣&#xff08;LeetCode&#xff09; 题解 方法一&#xff1a;设置两个指针&#xff0c;一个指针指向链表头结点&#…

云原生之深入解析Flink on k8s的运行模式与实战操作

一、概述 Flink 核心是一个流式的数据流执行引擎&#xff0c;并且能够基于同一个 Flink 运行时&#xff0c;提供支持流处理和批处理两种类型应用。其针对数据流的分布式计算提供了数据分布&#xff0c;数据通信及容错机制等功能。Flink 官网不同版本的文档flink on k8s 官方文…

辅助驾驶功能开发-功能对标篇(18)-NCA城市辅助系统-华为

1.横向对标参数 厂商华为车型极狐阿尔法S全新HI版上市时间2022/9/23方案13V5R3L+1DMS摄像头前视摄像头*4【双目+长焦+广角】侧视摄像头*4后视摄像头*1环视摄像头*4

993. 二叉树的堂兄弟节点

在二叉树中&#xff0c;根节点位于深度 0 处&#xff0c;每个深度为 k 的节点的子节点位于深度 k1 处。 如果二叉树的两个节点深度相同&#xff0c;但 父节点不同 &#xff0c;则它们是一对堂兄弟节点。 我们给出了具有唯一值的二叉树的根节点 root &#xff0c;以及树中两个…

leetcode496. 下一个更大元素 I

https://leetcode.cn/problems/next-greater-element-i/ nums1 中数字 x 的 下一个更大元素 是指 x 在 nums2 中对应位置 右侧 的 第一个 比 x 大的元素。 给你两个 没有重复元素 的数组 nums1 和 nums2 &#xff0c;下标从 0 开始计数&#xff0c;其中nums1 是 nums2 的子集…

CVE-2023-1454注入分析复现

简介 JeecgBoot的代码生成器是一种可以帮助开发者快速构建企业级应用的工具&#xff0c;它可以通过一键生成前后端代码&#xff0c;无需写任何代码&#xff0c;让开发者更多关注业务逻辑。 影响版本 Jeecg-Boot<3.5.1 环境搭建 idea 后端源码&#xff1a; https://git…

vue3项目创建(vite3+ts+elementui-plus)

文章目录 1.创建工程 1.创建工程 目的&#xff1a;vue3vitets 安装依赖&#xff0c;安装vite的工具 Vite下一代的前端工具链为开发提供极速响应v4.3 npm install -g create-vite创建工程 create-vite font-userui --template vue-ts –template vue-ts 后面的是配置模板&#…

git bash设置字体大小

背景 git bash默认字体太小了&#xff0c;每次读信息都要伸头盯着屏幕&#xff0c;很不自在&#xff0c;不符合我的风格&#xff0c;so let’s do it&#xff01; 修改前的git bash&#xff1a; 正确的打开方式 1、在任意目录下&#xff0c;右键选择“Git Bash Here”&…

ubuntu netplan工具原理(网络配置、ip修改ip、固定ip)(NetworkManager)

https://netplan.io/ 文章目录 netplan工作原理netplan -h原翻译命令释义- help&#xff1a;显示netplan的帮助消息。- apply&#xff1a;将当前netplan配置应用到运行系统。示例命令&#xff1a;netplan apply --debug- generate&#xff1a;从/etc/netplan/*.yaml生成特定于后…

前端面试题总结(二)

目录 1.localstorage和sessionstorage的区别2.for...in 和 for...of的区别3.深拷贝和浅拷贝的区别 以及深拷贝的方法4.同步和异步的区别5.堆和栈的区别6.基本数据类型有哪些7.对于this的理解8.前端性能优化9.watch和computed的区别10.Vue双向绑定原理11.谈谈闭包12.css有哪些计…

JVM 运行流程、类加载、垃圾回收

一、JVM 简介 1、JVM JVM 是 Java Virtual Machine 的简称&#xff0c;意为 Java 虚拟机。 虚拟机是指通过软件模拟的具有完整硬件功能的、运行在一个完全隔离的环境中的完整计算机系统。 常见的虚拟机&#xff1a;JVM、VMwave、Virtual Box。 JVM 和其他两个虚拟机的区别…

【深入了解PyTorch】PyTorch的优势

【深入了解PyTorch】PyTorch的优势 PyTorch的优势动态计算图Pythonic风格直观的调试和可视化社区支持和迁移能力PyTorch的特定应用场景优势自然语言处理(NLP)计算机视觉(CV)迁移学习和模型部署结论PyTorch的优势 深度学习框架在机器学习和深度学习领域中扮演着关键角色,而…

Android Java代码与JNI交互字符串转换(四)

🔥 Android Studio 版本 🔥 🔥 创建JNIString.java 🔥 package com.cmake.ndk1.jni;public class JNIString {static{System.loadLibrary("string-lib");}public native String callNativeString(String str);public native void stringMethod(String str)…

C/C++的发展历程和未来趋势

文章目录 C/C的起源C/C的应用C/C开发的工具C/C未来趋势 C/C的起源 C语言 C语言是一种通用的高级编程语言&#xff0c;由美国计算机科学家Dennis Ritchie在20世纪70年代初期开发出来。起初&#xff0c;C语言是作为操作系统UNIX的开发语言而创建的。C语言的设计目标是提供一种功…

【玩转循环】探索Python中的无限可能性

前言 循环可能是每个编程语言中使用比较多的语法了&#xff0c;如果能合理利用好循环&#xff0c;就会出现意想不到的结果&#xff0c;大大地减少代码量&#xff0c;让机器做那些简单枯燥的循环过程&#xff0c;今天我将为大家分享 python 中的循环语法使用。&#x1f697;&am…