If you're compiling in [c++17] mode only with a sufficiently recent compiler (e.g., GCC>=7, clang>=5, MSVC>=19.12), then everything is taken care by the compiler and you can stop reading.
如果你仅使用最新的编译器(例如,GCC>=7,clang>=5,MSVC>=19.12)在[c++17]模式下进行编译,那么编译器会处理所有问题,你可以停止阅读。
最近将算法部署到Linux上时,遇到Eigen内存对齐的问题,搜索了相关文章,做个简单的总结。
直接说结论:
1. 程序出了什么问题?
-- 由于Eigen内存对齐问题而导致程序崩溃。具体来说,在运行过程中,向量化运算按内存对齐的方式取Eigen对象的内存地址,而我们的程序中的那些Eigen对象并不是内存对齐的。
2. 程序为什么会出现这些问题?
--因为程序开启了向量化。向量化是一种加速运算方案,是计算机架构者提出,由C++编译器、处理器等支持实现。
--Eigen中有些对象(Fixed-size vectorizable Eigen objects,这些对象是静态分配,且字节size是16字节的倍数)刚好满足向量化运算的条件,而C++工程又在编译选项中开启了向量化,因此程序运行时实施了向量化。
--之所以有时候在Debug模式下程序运行正常,在Release则出现由于Eigen内存对齐导致的core dump,是因为Debug模式下没有开启向量化,而在Release模式下开启了向量化。
3. 为了避免或解决这些问题,Eigen做了什么?程序员还需要做什么?
--有两种解决方案,要么禁用向量化,从而即使Eigen中的Fixed-size vectorizable Eigen objects不是内存对齐的程序也不会出错;要么全面支持向量化。
--第一种方案除了会降低运算速度外,最重要的一点存在潜在风险,因为即使我们的程序中没有开启向量化,第三方库也可能开启向量化,这样同样会导致程序运行时崩溃。
--CMakeList开启向量化的编译指令是 -march=native。
--为了全面支持向量化,我们需要让C++程序中的Fixed-size vectorizable Eigen objects满足在声明时内存对齐,且在动态分配时也满足内存对齐。
--直接声明的对象是存储在栈(Stack)上的,其内存地址由编译器在编译时确定。可以通过预编译指令确保内存对齐。这一点由Eigen库予以保证。
--在动态分配时,对象的内存存在于堆(Heap)上,在运行时分配(动态分配)。C++的运行时库并不会关心预编译指令声明的对齐方式。一个有效的实践是重载对象的new运算符,new运算符会在堆上申请内存并调用对象的构造函数。可以在new 运输符申请内存时,通过特殊操作实现内存的对齐。幸运的时,Eigen库以宏的形式提供了new运算符EIGEN_MAKE_ALIGNED_OPERATOR_NEW,程序员只需要将这个宏放在C++类public的任何区域。
--对于element为Fixed-size vectorizable Eigen objects的STL 容器,Eigen提供了用于内存对齐的函数,确保这些容器中的Eigen对象的内存满足内存对齐要求。
例如:
std::vector<Eigen::Vector4d>
std::map<int, Eigen::Vector4d>
std::unordered_map<int, Eigen::Vector4d>
应该按下面的方式写:
#include <Eigen/StdVector>
std::vector<Eigen::Vector4d,Eigen::aligned_allocator<Eigen::Vector4d> >
std::map<int, Eigen::Vector4d, Eigen::aligned_allocator<std::pair<const int, Eigen::Vector4d> > >
template<class Key, class T>
using aligned_unordered_map = std::unordered_map<Key, T, std::hash<Key>, std::equal_to<Key>, Eigen::aligned_allocator< std::pair<const Key, T> > >;
aligned_unordered_map<int, Eigen::Vector4d>
-- 还应该注意什么:Eigen对象传参时,应该传引用,而不应该传值,否则仍有可能引发内存对齐问题。
4. CMakeLists开启向量化的方法
find_package(Eigen3 3.3 REQUIRED CONFIG)
include(CheckCXXCompilerFlag)
check_cxx_compiler_flag("-march=native" _march_native_works)
check_cxx_compiler_flag("-xHost" _xhost_works)
set(_CXX_FLAGS)
if(_march_native_works)
message(STATUS "Using processor's vector instructions (-march=native compiler flag set)")
set(_CXX_FLAGS "-march=native")
elseif(_xhost_works)
message(STATUS "Using processor's vector instructions (-xHost compiler flag set)")
set(_CXX_FLAGS "-xHost")
else()
message(STATUS "No suitable compiler flag found for vectorization")
endif()
add_executable(linear-algebra linear-algebra.cpp)
target_compile_options(linear-algebra
PRIVATE
${_CXX_FLAGS}
)
target_link_libraries(linear-algebra
PRIVATE
Eigen3::Eigen
)
参考:
[1] Eigen: Alignment issues
Eigen: Fixed-size vectorizable Eigen objects
Eigen: Structures Having Eigen Members
Eigen: Using STL Containers with Eigen
Eigen: Passing Eigen objects by value to functions
[2] 从Eigen向量化谈内存对齐 - 知乎 (zhihu.com)
[3] 编译优化之 - 向量化优化入门_zhugl0的博客-CSDN博客_编译器函数向量化
[4] 第2章 检测环境 - 2.6 为Eigen库使能向量化 - 《CMake菜谱(CMake Cookbook中文版)》 - 书栈网 · BookStack