0%

使用数据对齐以提高内存系统的性能

原理介绍

计算机系统对基本数据类型的合法地址做出了对齐限制,要求某种类型对象的地址必须是某个值 K 的倍数,K 值通常取 2、4 或者 8,这就叫做数据对齐

数据对齐的作用是,简化形成处理器和内存系统之间接口的硬件设计,减少内存操作,降低内存访问的次数

数据对齐的原则:
1)任何 K 字节的基本对象的地址必须是 K 的倍数,即每种类型的对象都需要满足它的对齐限制
2)为了实现数据对齐,编译器在字段的分配中插入间隙
3)结构本身对它的起始地址也有一些对齐要求
4)编译器需要保证指向结构体的指针也满足对齐要求
5)为了使得结构体数组中的每个元素也满足它的对齐要求,编译器在结构体的末尾也进行一些填充,其填充的字节数由结构体中的元素的最大 K 值决定

在 64 位机器上 K 作如下取值

K 类型
1 char
2 short
4 int, float
8 long, double char*

实例分析

例 1

考虑下面的结构体声明

1
2
3
4
5
struct S1 {
int i; // 4
char c; // 1
int j; // 4
}

假设编译器用最小的 9 字节分配,则字段 i 的偏移为 0,字段 c 的偏移为 4,字段 j 的偏移为 5,这样的分配方式并不满足数据对齐的要求

我们可以在字段 c 和 j 之间插入一个 3 字节的间隙,这样就使得 j 的偏移增加到了 8,整个结构体的大小变为了 12 字节,这样就满足了数据对齐的要求

图示

例 2

考虑下面的结构体声明

1
2
3
4
5
struct S1 {
int i; // 4
int j; // 4
char c; // 1
}

若将这个结构体打包成 9 个字节,只要保证结构的起始地址满足 4 字节对齐要求,我们仍然能够保证满足字段 i 和 j 的对齐要求,但如果用 S2 作如下声明

1
struct S2 d[4];

我们发现,只分配 9 个字节,使得 d[0] 的地址为 $x_d$,d[1]、d[2]、d[3] 的地址分别为 $x_d + 9$、$x_d + 18$、$x_d + 27$,不能满足结构体数组 d 中每个元素的对齐要求,于是编译器会在字段 c 后填充 3 个字节,即为结构体 S2 分配 12 个字节,使得 d[1]、d[2]、d[3] 的地址分别为 $x_d + 12$、$x_d + 24$、$x_d + 36$,那么只要 $x_d$ 是 4 的倍数,所有的对齐限制就都可以满足了

图示

练习题

对下面每个结构体声明,确定每个字段的偏移量,结构体总的大小,以及在 x86-64 下它的对齐要求

1
2
3
4
5
A. struct P1 { int i; char c; int j; char d; };
B. struct P2 { int i; char c; char d; long j; };
C. struct P3 { short w[3]; char c[3]; };
D. struct P4 { short w[5]; char *c[3]; };
E. struct P5 { struct P3 a[2]; struct P2 t; };

A.
字段 i 偏移量为 0
字段 c 偏移量为 4
字段 j 偏移量为 8
字段 d 偏移量为 12
结构体总的大小为 16 字节
对齐要求为 4 字节对齐

B.
字段 i 偏移量为 0
字段 c 偏移量为 4
字段 d 偏移量为 5
字段 j 偏移量为 8
结构体总的大小为 16 字节
对齐要求为 8 字节对齐

C.
字段 w 的偏移量为 0
字段 c 的偏移量为 6
结构体总的大小为 10 字节
对齐要求为 2 字节对齐

D.
字段 w 的偏移量为 0
字段 c 的偏移量为 16
结构体总的大小为 40 字节
对齐要求为 8 字节对齐

E.
字段 a 的偏移量为 0
字段 t 的偏移量为 24
结构体总的大小为 40 字节
对齐要求为 8 字节对齐

对于下列结构声明回答后续问题

1
2
3
4
5
6
7
8
9
10
struct {
char *a; // 8
short b; // 2
double c; // 8
char d; // 1
float e; // 4
char f; // 1
long g; // 8
int h; // 4
} rec;

这个结构体中所有的字段的字节偏移量是多少?
这个结构体总的大小是多少?
重新排列这个结构中的字段,以最小化浪费的空间,然后再给出重排过的结构的字节偏移量和总的大小

字段 a 的偏移量为 0
字段 b 的偏移量为 8
字段 c 的偏移量为 16
字段 d 的偏移量为 24
字段 e 的偏移量为 28
字段 f 的偏移量为 32
字段 g 的偏移量为 40
字段 h 的偏移量为 48

填充 4 个字节以满足对 8 字节对齐的要求
结构体总的大小为 56 个字节

重新排列后为

1
2
3
4
5
6
7
8
9
10
struct {
char *a; // 8
double c; // 8
long g; // 8
float e; // 4
int h; // 4
short b; // 2
char d; // 1
char f; // 1
} rec;

字段 a 的偏移量为 0
字段 c 的偏移量为 8
字段 g 的偏移量为 16
字段 e 的偏移量为 24
字段 h 的偏移量为 28
字段 b 的偏移量为 32
字段 d 的偏移量为 34
字段 f 的偏移量为 35

填充 4 个字节以满足对 8 字节对齐的要求
重新排列后结构体总的大小为 40 个字节