问题描述
当用户代码和库代码没有同时使用编译选项 -march=native时,在运行时经常会报 double free error或者其他的内存相关错误。但是在某些时候,这个编译选项又不会带来错误。
经过更多测试,发现这个问题发生在接口类存在Eigen数组作为成员变量的时候。因此需要对这个问题进行原理理解。
太长不看(解决方法)
接口类当中的eigen成员变量都声明不做对齐,这样就解决了用户代码和库编译时指令集加速方式不一致时带来的不同对齐方式的冲突。如,使用Eigen::Matrix<float, 2, 1, Eigen::DontAlign> 取代Eigen::Vector2f
Eigen内存对齐
eigen内存需要额外的对齐,进而才能使用eigen调用的各类加速指令集等。因此,动态、静态的eigen矩阵,包含eigen的结构体、类,都需要额外的对齐命令才能正常工作(构造、运算、析构)。
详情见:
http://eigen.tuxfamily.org/dox/group__DenseMatrixManipulation__Alignement.html
目前影响我们的部分有:
- EIGEN_MAKE_ALIGNED_OPERATOR_NEW 与 C++17:C++17中,自动对包含eigen类、结构体的构造函数进行对齐的操作,因此 EIGEN_MAKE_ALIGNED_OPERATOR_NEW 宏此时为空
- 固定大小的eigen矩阵默认会进行16位对齐(有指令集加速时表现可能不一致!!!见下一节)
- 作为类成员变量的动态大小的eigen矩阵不影响类本身的构造和析构;但是动态大小的eigen矩阵本身的构造和析构仍然有自己的对齐问题(见指令集加速)
- 将eigen矩阵传值进入函数是非常不好的方式。效率和内存正确性都可能受影响。建议使用常量引用、Eigen::Ref或者Eigen::Map等复制成本极低的方式
Eigen指令集加速
不同的编译选项会开启eigen不同的指令集加速;不同的指令集加速的下,需要不同的对齐方式。
原始文档见:
http://eigen.tuxfamily.org/index.php?title=FAQ#How_can_I_disable_vectorization.3F
SIMD并行化
你只需要告诉你的编译器启用相应的指令集,然后 Eigen 就会检测到它。如果它默认启用,则您无需执行任何操作。在 GCC 和 clang 上,您可以简单地传递 -march=native 让编译器启用您的 CPU 支持的所有指令集。
- 在 x86 架构上,大多数编译器默认不启用 SSE。您需要手动启用 SSE2(或更新版本)。例如,对于 GCC,您将传递 -msse2 命令行选项。在x86-64架构上,SSE2一般默认是开启的,但是你可以开启AVX和FMA以获得更好的性能 在 PowerPC 上,您必须使用以下标志:-maltivec -mabi=altivec,用于 AltiVec,或 -mvsx 用于支持 VSX 的系统。
- 在 32 位 ARM NEON 上,以下内容:-mfpu=neon -mfloat-abi=softfp|hard,具体取决于您使用的是 softfp/hardfp 系统。大多数当前发行版都使用硬浮点 ABI,所以选择后者,或者保留默认值并传递 -mfpu=neon。
- 在 64 位 ARM 上,默认情况下启用 SIMD,您无需执行任何额外操作。
- 在 S390X SIMD (ZVector) 上,您必须使用最新的 gcc(版本 >5.2.1)编译器,并添加以下标志:-march=z13 -mzvector。
指令集与对齐
固定大小的eigen矩阵通常非常小,这意味着我们希望以零运行时开销来处理它们——无论是在内存使用还是速度方面。
现在,并行化适用于 128 位数据包(例如 SSE、AltiVec、NEON)、256 位数据包(例如 AVX)或 512 位数据包(例如 AVX512)。此外,出于性能原因,如果这些数据包与数据包大小具有相同的对齐方式,即分别为 16 字节、32 字节和 64 字节,则读取和写入这些数据包的效率最高。所以事实证明,固定大小的 Eigen 对象可以被矢量化的最佳方式是,如果它们的大小是 16 字节(或更多)的倍数。然后,Eigen 将为这些对象请求 16 字节对齐(或更多),并从此依赖对齐这些对象来实现最大效率。
Eigen在接口代码建议
由于用户的代码和库代码在编译时可能有不同的编译选项,进而导致不同的对齐方式,不妨直接禁止接口类中的eigen成员变量使用向量化的对齐,从而在牺牲接口类的部分性能的情况下,兼顾通用性;同时,内部的实现代码仍然可以启用向量化操作,从而保证效率。
一种可能的实践方式
在implementation的内部,仍然使用平时的加速方式;在接口类中,所有的eigen::matrix都加上eigen::dontalign的选项,如:
#include <Eigen/Dense>
struct Object {
Eigen::Matrix<float, 2, 1, Eigen::DontAlign> coordinate;
Eigen::Matrix<float, -1, 4, Eigen::DontAlign> corners;
}
注意:Eigen::Quaternion等 <Eigen/Geometry> 中的对象,不适用于Eigen::DontAlign选项,使用请查询文档!