缘由
这一段好几次纠缠于结构体成员字节对齐问题,经过一段时间的积累,慢慢就有了想说的东西
结论先行
- 在更多的场景下,应该按照编译器默认字节对齐,对结构体进行编码
- 在少量需要协议对齐、抑或外部接口要求的场景,才应该使用某种长度的字节对齐
- 对于内存敏感的场景,首先应该考虑如何规避编译器默认字节对齐带来的膨胀影响,例如,调整结构体成员顺序,同时需要兼顾频繁访问内存的缓存
Cache lined
要求,以及成员语义内聚问题
- 最后,在极少数情况下,使用一字节对齐。
一字节对齐
在非精细化研发的情况下,容易出现意想不到的字节不对齐问题,造成内存访问低效率情况
证据链
Linux
内核中代码大量结构体,也未启用强制字节对齐
编译器默认字节对齐,引发的内存膨胀问题,不应该首先考虑的范围内,它是优化的一部分,应避免过早优化
动态申请内存字节对齐快速计算方法
在常用操作上,存在四字节对齐、八字节对齐等场景。这些场景,因为对齐长度均是2
的幂次,存在较为快速的计算算法,也很直观。
#define ROUND_UP4(n) (((n) + 3) & ~3)
#define ROUND_UP8(n) (((n) + 7) & ~7)
//用void*指针来确定机器字长
#define ROUND_UP_MACHINE() (((n) + (sizeof(void*) - 1)) & ~(sizeof(void*) - 1))
对齐其它
2
的幂次的长度对齐,可以类比扩展
不同字节对齐结构体混用问题
对于在动态申请内存上,通过,强制转换指针,堆叠两个以上的结构体进行操作,特别是,存在与非编译器默认字节对齐的结构体换用情况,需要特别注意每个结构体堆放的起始位置,才可以不诱发字节不对齐引起的问题!
尽量不混用字节对齐不一致的结构体在同一份动态申请内存块中
试验程序
测试代码中,printf
访问具有类似的成员u64
的结构体,虽然在汇编代码层面上,两个打印过程除了必要的参数变化,其它汇编代码都一样, 但是执行消耗时间,对于字节不对齐的结构体大量读写访问,最终消耗时间会大一些!
见测试代码
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <sys/time.h>
typedef struct A {
uint8_t u8;
uint64_t u64;
} A;
/*Packed 编译指令*/
#pragma pack(push, 1)
typedef struct B {
uint8_t u8;
uint64_t u64;
} B;
#pragma pack(pop)
typedef struct C {
uint8_t u8;
uint64_t u64;
} C;
double getDiff(const struct timeval *start, const struct timeval *end);
double getDiff(const struct timeval *start, const struct timeval *end)
{
#define USEC_UNIT (1000000)
unsigned long diff = (end->tv_sec - start->tv_sec) * USEC_UNIT + (end->tv_usec - start->tv_usec);
return (double)diff/USEC_UNIT;
}
int main(int argc, char **argv)
{
struct timeval start, end;
double smalldiff, bigdiff;
printf("Sizeof(A): %2zu in default\nSizeof(B): %2zu with pack(1)\nSizeof(C): %2zu afer pack setting switch\n", sizeof(A), sizeof(B), sizeof(C));
A a = {};
B b = {};
printf("\nThe asm code framewrok is the same, but the consumed time is different.\n");
printf("Access A member: %lu to see its asm code\n", a.u64);
printf("Access B member: %lu to see its asm code\n", b.u64);
const unsigned int count = 100000000;
printf("\nAccess struct member in default pack setting\n");
unsigned int i = count;
gettimeofday(&start, NULL);
while(i--)
{
a.u64 += a.u8;
++a.u8;
}
gettimeofday(&end, NULL);
smalldiff = getDiff(&start, &end);
printf("End time: %ld.%ld, diff: %6lf in pack(default) setting\n", end.tv_sec, end.tv_usec, getDiff(&start, &end));
i = count;
gettimeofday(&start, NULL);
while(i--)
{
b.u64 += b.u8;
++b.u8;
}
gettimeofday(&end, NULL);
bigdiff = getDiff(&start, &end);
printf("End time: %ld.%ld, diff: %6lf in pack(1) setting\n", end.tv_sec, end.tv_usec, getDiff(&start, &end));
printf("Diff Percent: %lf%%\n", (bigdiff - smalldiff)*100/bigdiff);
return 0;
}