一、PointXYZ和PointXYZI在PCL中的定义
写这篇文章主要是加深下自己对PCL基础数据结构的理解。这里主要讲解PointXYZ和PointXYZI,其他的类型如PointXYZRGBA等可以类推。
这两个类型包含成员变量分别是
PointXYZ - float X,Y,Z
PointXYZI - float X,Y,Z,Intensity
这里的Intensity 属性通常表示了点的强度或反射强度。它表示点反射回传感器的光强度或能量。在激光扫描和三维测量中,intensity 属性可以用来描述点的反射特性,例如,亮度较高的点通常表示有反光表面,而亮度较低的点表示表面可能吸收了更多的光线。
如果没看PCL源码,可能大部分人会这样在程序中定义它们
struct PointXYZ { float x; float y; float z; }
struct PointXYZI { float x; float y; float z; float intensity; }
这样写固然可以,但是对于运算效率较高的点云处理来说,这种简单结构明显不适合。下面说下在PCL中是两者怎么定义的,我截取了point_types.hpp中的关于PointXYZ定义的代码片段
struct _PointXYZ
{
PCL_ADD_POINT4D; // 这里在下面一个代码片段中展开
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
struct EIGEN_ALIGN16 PointXYZ : public _PointXYZ
\\此处省略后面的代码
struct EIGEN_ALIGN16 _PointXYZI
{
PCL_ADD_POINT4D;
union
{
struct
{
float intensity;
};
float data_c[4];
};
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
struct PointXYZI : public _PointXYZI
\\此处省略后面的代码
其中上述代码的PCL_ADD_POINT4D是如下两个宏合并后的宏,也就是PointXYZ是这样的。同时也给出
#define PCL_ADD_POINT4D \
PCL_ADD_UNION_POINT4D \
PCL_ADD_EIGEN_MAPS_POINT4D
#define PCL_ADD_UNION_POINT4D \
union EIGEN_ALIGN16 { \
float data[4]; \
struct { \
float x; \
float y; \
float z; \
}; \
};
};
看了源码后,那为什么在PointXYZ要使用union类型,尤其是PointXYZI还使用两个。下面讲解下个人的见解,如果有不对的地方,麻烦指正。
二、Union在PCL中的用处
首先,union 是C/C++语言中的一个关键字,用于创建联合体(union)。联合体是一种特殊的数据结构,它允许在相同的内存位置存储不同类型的数据,但只能同时存储其中的一个。union 的在PCL中主要作用是节省内存,特别是在需要存储多种类型数据但只会使用其中一种时,比如可以直接point.x,point.y,point.z这样获取xyz坐标,也可以直接根据数组指针data去拿。下面举例说明下union的用法:
union myun
{
struct { int x; int y; int z; }u;
int k;
} a;
int main()
{
a.u.x =4;
a.u.y =5;
a.u.z =6;
a.k = 0;
printf("%d %d %d\n",a.u.x,a.u.y,a.u.z);
return 0;
}
这里输出的是0,5,6。因为union类型是共享内存的,以size最大的结构作为自己的大小,这样的话,myun这个结构就包含u这个结构体,而大小也等于u这个结构体 的大小,在内存中的排列为声明的顺序x,y,z从低到高,然后赋值的时候,在内存中,就是x的位置放置4,y的位置放置5,z的位置放置6,现在对k赋 值,对k的赋值因为是union,要共享内存,所以从union的首地址开始放置,首地址开始的位置其实是x的位置,这样原来内存中x的位置就被k所赋的 值代替了,就变为0了。这个时候要进行打印,就直接看内存里就行了,x的位置也就是k的位置是0,而y,z的位置的值没有改变,所以是0,5,6。
在PointXYZI中虽然使用两个结构体存放xyz和intensity,存储效率非常低。但使用union与struct相结合也有优点,它采用Eigen嵌入的内存对齐方式EIGEN_ALIGN16,也就是16字节对齐,提高了运行效率。而且point中也不能直接把intensity放在xyz后面,因为大部分情况下数组元素置为0或1,容易将其覆盖。比如:在点乘时最后一个data[3]元素会被置0。所以采用两个结构体,用额外三个float类型数据填补intensity。
下面简单介绍下内存对齐在这里的优势:
1. **性能提升:** 内存对齐可以提高数据访问速度。许多计算机体系结构要求数据按照特定的字节边界对齐,否则可能导致额外的处理器周期来读取数据。正确对齐的数据可以减少内存访问时间,提高程序性能
2. **硬件要求满足:** 一些硬件平台要求数据按照特定的对齐方式存储,否则可能导致硬件异常或错误。通过内存对齐,可以确保程序在不同平台上的正确性。
3. **缓存性能:** 缓存是计算机中的重要性能优化因素。内存对齐可以帮助数据更好地利用缓存行(cache line),减少不必要的缓存行填充和刷新,从而提高缓存性能。
4. **SIMD指令集优化:** SIMD(Single Instruction, Multiple Data)指令集(如SSE、AVX等)在处理大规模数据时非常高效。正确对齐数据可以允许编译器和硬件执行更有效的SIMD操作,提高并行性能。
5. **结构体和类的内存布局:** 在C++等语言中,结构体和类的成员通常会按照它们的声明顺序进行内存排列。通过合适的内存对齐设置,可以避免填充字节,从而减少内存的浪费。
6. **平台移植性:** 内存对齐可以提高程序在不同平台上的可移植性。通过确保数据按照规范的对齐方式存储,可以减少平台相关性问题。
总之,内存对齐是一项重要的性能和可移植性优化技术,可以在许多情况下提高程序的效率,并确保程序在不同硬件平台上的正确性。然而,要注意不同编译器和平台可能有不同的内存对齐规则,因此在需要时,可以使用编译器提供的对齐控制选项来精确控制内存对齐。