CPU 存取原理
一、“存”示例
- CPU 并不是以字节为单位存取数据的。CPU把内存当成是一块一块的,块的大小可以是2,4,8,16字节大小,因此CPU在读取内存时是一块一块进行读取的。每次内存存取都会产生一个固定的开销,减少内存存取次数将提升程序的性能。
- CPU 一般会以 2/4/8/16/32 字节为单位来进行存取操作,我们将这些存取单位也就是块大小称为(memory access granularity)内存存取粒度。
假设有以下数据:
那么 CPU 存储之后如下:
二、“取”示例
在一个存取粒度为 4 字节的内存中,先从地址 0 读取 4 个字节到寄存器,然后从地址 1 读取 4 个字节到寄存器:
-
当从地址 0 开始读取数据时,是读取对齐地址的数据,直接通过一次读取就能完成;当从地址 1 读取数据时读取的是非对齐地址的数据,需要读取两次数据才能完成。
-
在读取完两次数据后,还要将 0-3 的数据向上偏移 1 字节,将 4-7 的数据向下偏移 3 字节,最后再将两块数据合并放入寄存器。
-
对一个内存未对齐的数据进行了这么多额外的操作,这对 CPU 的开销很大,大大降低了CPU性能。
内存对齐简介
一、概念
① 什么是内存对齐?
- 内存对齐是一种在计算机内存中排列数据(表现为变量的地址)、访问数据(表现为CPU读取数据)的一种方式。
- 内存对齐包含了两种相互独立又相互关联的部分:基本数据对齐和结构体数据对齐 。
② 为什么要进行内存对齐?
- 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
- 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
③ 内存对齐原则
在 iOS 中,对象的属性需要进行内存对齐,而对象本身也需要进行内存对齐。内存对齐有三原则:
- 数据成员对齐原则: 结构( struct )(或联合( union ))的数据成员,第一个数据成员放在 offset 为 0 的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小。
- 结构体作为成员: 如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储。(如:struct a⾥存有struct b,b⾥有char、int 、double等元素,那b应该从8的整数倍开始存储)
- 收尾工作: 结构体的总大小,也就是 sizeof 的结果,必须是其内部最大成员的整数倍,不足的要补⻬。
简而言之:
- 前面的地址必须是后面的地址正数倍,不是就补齐;
- 结构体里面的嵌套结构体大小要以该嵌套结构体最大元素大小的整数倍;
- 整个 Struct 的地址必须是最大字节的整数倍。
④ 特别说明
- 在字节对齐算法中,对齐的主要是对象,而对象的本质则是一个 struct objc_object 的结构体;
- 结构体在内存中是连续存放的,所以可以利用这点对结构体进行强转;
- 苹果早期是8字节对齐,现在是16字节对齐。
二、C/OC 基本数据类型的内存大小(字节)
C | OC | 32位 | 64位 |
---|---|---|---|
bool | BOOL(64位) | 1 | 1 |
signed char | (_signed char)int8_t、BOOL(32位) | 1 | 1 |
unsigned char | Boolean | 1 | 1 |
short | int16_t | 2 | 2 |
unsigned short | unichar | 2 | 2 |
int、int32_t | NSInteger(32位)、boolean_t(32位) | 4 | 4 |
unsigned int | NSUInteger(32位)、boolean_t(64位) | 4 | 4 |
long | NSInteger(64位) | 4 | 8 |
unsigned long | NSUInteger(64位) | 4 | 8 |
long long | int64_t | 8 | 8 |
float | CGFloat(32位) | 4 | 4 |
double | CGFloat(64位) | 8 | 8 |
结构体的内存对齐
一、常规结构体
二、嵌套结构体
未完待续