Skip to content

Latest commit

 

History

History
115 lines (86 loc) · 3.18 KB

README.md

File metadata and controls

115 lines (86 loc) · 3.18 KB

Rust 中内存对齐

如果你曾读 CSAPP 一书,那你一定记得 CSAPP 中关于 C 语言内结构体内存对齐的描述。

内存对齐的背景和概念

当 CPU 对数据进行访问时,每次都是取指定的字节数。例如每次取 4 字节,若当前数据类型为 int, 且地址位为 0x00-0x03, 则一次性可以取出该变量;若该变量的地址为 0x02-0x05, 则会分别取0x00-0x03, 0x04-0x07 两个地址,因此会存在一定的性能消耗。

对此,可以采用内存对齐策略来优化存储,以 C 语言结构体为例:

struct ss {
 int i;
 char c;
 int j;
 char d;
} test = {
 1,
 'a',
 2,
 'b',
};

int main() {
 printf("The size of test: %lu", sizeof(test)); // 16
}

首先思考,结构体 ss 中,两个 int 类型,两个 char 类型,一共占据 10 字节。

但是,由于内存对齐策略,实际存储到内存中,char 类型需要对齐 int 类型的 4 字节,因此结构体 ss 一共占据 16 字节。

其示例图如下:

0x00 - 0x03 i0 i1 i2 i3 // 一个 int 变量占据四字节
0x04 - 0x07 c0          // 一个 char 变量占据一字节
0x08 - 0x0B j0 j1 j2 j3 // 但由于内存对齐,会对齐 int 类型的 4 字节
0x0C - 0x0F d0

以上,就是内存对齐的背景和概念。

优化 C 语言中结构体

上例中,为了优化运行耗时,内存对齐策略将原本只有 10 字节的结构体扩展到了 16 字节,此时,可以修改结构体内变量的排序来优化内存:

struct ss2 {
 int i;
 char c;
 char d;
 int j;
} test2 = {
 1,
 'a',
 'b',
 2,
};

int main() {
 printf("The size of test2: %lu", sizeof(test2)); // 12
}

结构体 ss2 生成的变量内存占据 12 字节,这是因为,char 类型的变量 d 使用了变量 c 中未使用的部分,其示例如下:

0x00 - 0x03 i0 i1 i2 i3
0x04 - 0x07 c0 d0      
0x08 - 0x0B j0 j1 j2 j3 

Rust 中的内存对齐

在 Rust 中,也有结构体的概念,但是在内存对齐的处理上与 C 语言有所不同,例如:

struct A {
 i: i32,
 u1: u8,
 j: i32,
 u2: u8,
}

fn main() {
 println!("The size of struct A: {:?}", std::mem::size_of::<A>()); // 12 
}

在 Rust 中, char 类型占据 4 个字符,因此使用 u8 代替 char.

上述代码中,i32 类型占据 4 个字节,u8 类型占据 1 个字节,两个 i32 和两个 u8 一共占据 10 个字节。

同时,按照结构体 A 的排列,遵从 C 语言的逻辑来讲,应该是占据 16 字节,但是,Rust 编译器会对结构体进行字段重排,优化内存占用,因此其字节数位 12.

如果想要禁止掉编译器的优化,则需要使用内存布局属性 repr, 其示例如下:

#[repr(C)]
struct B {
 i: i32,
 u1: u8,
 j: i32,
 u2: u8, 
}

fn main() {
 println!("The size of struct B: {:?}", std::mem::size_of::<B>()); // 16
}

感悟

这个示例并无太大难度,但是可以留下一些思考:在实际开发中,得益于高度完善的编译器、解释器等工具,开发者可以将更多的精力放到对工程代码的可读性、正确性上,而没有必要过多担心某语句、某变量、某声明是否会对运行时间带来影响。