定义数据结构时,成员变量优先采用默认对齐的方式
【说明】 定义数据结构时,采用默认对齐的方式,可以提高数据访问的效率;需要注意的是,如果是跨CPU/子系统/模块的消息数据结构定义,在遵从接口契约的基础上,优先采用默认对齐的方式定义;
【原理】 数据结构对齐是代码编译后在内存的布局方式,这种布局方式的差异,会导致CPU在访存过程中的开销存在差异——如果被访问的内存地址不按照被访问的数据类型的位宽对齐(所谓对齐,即被访问字段的长度能够整除其起始地址,例如长度为4个字节的int类型数据,如果其地址为0x12345678则认为是对齐的,如果其地址为0x12345677,则不是对齐的),则可能触发总线错误,或导致数据读取低效。
针对不支持非对齐数据访问指令的CPU(例如MIPS架构和Arm v5架构等),如果需要进行非对齐的数据访问场景,需要通过编译器生成多条数据访问读取指令后拼接。以32位系统为例,CPU在处理数据读写操作时,会以四字节的起始地址读取,此时如果目标数据跨越了四字节对齐边界,则会导致CPU从一次读取变为分两次读取后再拼接,导致代码的指令cycle数抬升。
针对上述非对齐访问可能带来的问题,可以采用如下方式进行调整:
- 调整数据结构的顺序:将其从非对齐访问的方式调整为对齐访问的方式(如下将DemoOld优化未DemoNew),但是需要注意的是,如果字段之间存在逻辑上的关联,调整字段的定义顺序可能导致业务上的混淆。
#pragma pack(1) struct DemoOld{ char a; short b; int c; }; #pragma pack()调整为#pragma pack(1) struct DemoNew{ int c; short b; char a; }; #pragma pack()
- 手动填充补齐(padding):在字段中增加用于对齐的额外字节,使得非对齐访问的字段可以实现对齐访问。
#pragma pack(1) struct DemoOld{ char a; short b; int c; }; #pragma pack()调整为#pragma pack(1) struct DemoNew{ char a; char reserved; short b; int c; }; #pragma pack()
- 编译器字节对齐(align/pack):利用预处理指令(如#pragma pack(n),n可以设置为1/2/4/8/16),在编译阶段指示提示编译器进行字段对齐;具体地,编译器还会将pack(n)中的n与当前字段的默认对齐字节数进行比较,取其较小的为最终对齐字节数。
#pragma pack(1) struct DemoOld{ char a; short b; int c; }; #pragma pack()调整为#pragma pack(4) struct DemoOld{ char a; short b; int c; }; #pragma pack()
【注意事项】 默认对齐相对于紧凑排列可能导致额外的内存开销,因此需要在内存开销和性能表现之间权衡。
【案例】
优化前:
#pragma pack(1)
struct DemoStructItem {
uint32_t status;
};
struct DemoStruct {
uint16_t num;
DemoStructItem items[20];
};
#pragma pack()
说明:DemoStruct结构的items中每个元素当前未按照四字节对齐,因此CPU每次读取items的单个元素都需要分两次读取后再拼接使用。
优化后:
#pragma pack(1)
struct DemoStructItem {
uint32_t status;
};
struct DemoStruct {
uint16_t num;
uint8_t rsvd[2];
DemoStructItem items[20];
};
#pragma pack()
说明:DemoStruct结构中增加2字节保留字段对齐,确保items中每个元素按照四字节对齐。
父主题: 数据结构