iOS底层之内存对齐算法解析

目前但凡一个iOS岗面试都会问个内存对齐问题,那么什么是字节对齐?成员变量对齐和对象内存对齐有什么区别?今天我来为大家一一解析

iOS创建对象的_class_createInstanceFromZone方法中会通过instanceSize方法计算创建对象需要开辟的内存空间(细创建对象流程点我),源码如下:

inline size_t instanceSize(size_t extraBytes) const {
        if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
            return cache.fastInstanceSize(extraBytes);
        }

        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16 bytes.
        if (size < 16) size = 16;
        return size;
    }

alignedInstanceSize()源码如下:

uint32_t alignedInstanceSize() const {
        return word_align(unalignedInstanceSize());
    }

通过分析源码得出以下结论:

  1. 对象开辟的空间大小完全取决于对象的成员变量(depending on class's ivars),通过成员变量得出未对齐的内存大小:
// May be unaligned depending on class's ivars.
uint32_t unalignedInstanceSize() const {
        ASSERT(isRealized());
        return data()->ro()->instanceSize;
    }
  1. 若对象没有成员变量,则属性只有从基类NSObject继承来的isa,isa为一个class类型的结构体指针Class isa OBJC_ISA_AVAILABILITY;,因此一个对象最小的未对齐空间unalignedInstanceSize为8。

  2. 得到unalignedInstanceSize以后,通过word_align方法将其字节对齐,接下来逐步分析具体成员变量对齐方法

  • 对齐方法代码:
static inline uint32_t word_align(uint32_t x) {
    return (x + WORD_MASK) & ~WORD_MASK;
}

其中WORD_MASK定义为define WORD_MASK 7UL

  • 假设当前对象没有成员变量,则x = 8,以上表达式可转化为:
   (8 + 7) & ~7
->  15 & ~7
  • 转为二进制则为:
15      : 0000 1111
7       : 0000 0111 
~7      : 1111 1000
15 & ~7 : 0000 1000 = 8

一个无成员变量的对象通过对齐得到的alignedInstanceSize为8。

  • 字节对齐算法的意义为8字节对齐,取8的整数倍。目的在于在计算读取成员变量的时候,如果成员变量根据实际类型设定大小,会影响计算机读取速度,但如果统一以8字节为度量衡,可以加快读取速度(以空间换取时间)这个算法也可以用以下方式表达:
  (8 + 7) >> 3 << 3           右移3位再左移三位
  15 : 0000 1111
  右移三位 -> 0000 0001
  左移三位 -> 0000 1000 = 8
  • 得到alignedInstanceSize后,instanceSize最后会通过if (size < 16) size = 16条件将小于16的结果全部转为16,至此,一个无成员变量的对象开辟的空间为16。

4.接下来分析对象带有多个成员变量的情况:

@property (nonatomic,copy)NSString *name;

@property (nonatomic,assign)int age;

@property (nonatomic,assign)long height;

@property (nonatomic,copy)NSString *nickName;

@property (nonatomic,assign)BOOL isMale;

并进行赋值:

p.name = @"FC";
p.age = 18;
p.height = 185;
p.nickName = @"XX";
p.isMale = YES;

在控制台通过x/6gx p 来打印对象p的详细内存情况:


image.png

其中第一段内存为对象的isa,第二段内存中,系统将属性age和属性isMale两个4字节的属性进行了组合,优化了内存分配,打印结果如图:

image.png

5.内存对齐原则:

  • 数据成员对⻬规则:结构(struct)(或联合(union))的数据成员,第⼀个数据成员放在offset为0的地⽅,以后每个数据成员存储的起始位置要
    从该成员⼤⼩或者成员的⼦成员⼤⼩(只要该成员有⼦成员,⽐如说是数组,结构体等)的整数倍开始(⽐如int为4字节,则要从4的整数倍地址开始存储。

  • 结构体作为成员:如果⼀个结构⾥有某些结构体成员,则结构体成员要从
    其内部最⼤元素⼤⼩的整数倍地址开始存储.(struct a⾥存有struct b,b
    ⾥有char,int ,double等元素,那b应该从8的整数倍开始存储.)

  • 收尾⼯作:结构体的总⼤⼩,也就是sizeof的结果,.必须是其内部最⼤
    成员的整数倍.不⾜的要补⻬。

举例:


image.png

以上两个结构体内存大小是否一致?答案:不一致,分别为24和16。
为什么同样的成员变量,因为顺序不同内存结果不同?
依据原则1”每个成员变量储存的起始位置需要可以整除该成员变量的字节大小“,得出两个结构体的储存流程如下:


image.png

image.png

再依据原则3,内存结果必须为最大成员的整数倍,得出结果为24和16。

为什么每个成员变量起始位置必须要是自己的整数倍呢?


image.png

不做对齐操作时,计算机读取此结构体变量需要读取4次,每次读取大小分别为8,1,2,4,读取次数多,每次读取次数不同,降低读取效率。

image.png

内存对齐操作后,可以看到后面3个成员变量组合到了一起,并且系统会告知该组合中含有大小为1,2,4的成员变量,计算机再读取此结构体的时候只需以8字节为单位读取两次,大大提高了读取效率,这就是需要内存对齐的原因啦

6.对象内存最终结果

  • 创建一个类:


    image.png
  • 如图赋值并进行打印:


    image.png
  • 打印结果为:


    image.png

按照之前的分析:
sizeof(p)结果为8(p为结构体指针,大小为8);
class_getInstanceSize([FCPerson class])为成员变量大小+isa,结果为40
malloc_size是什么?为什么等于48?

通过研究malloc源码, 在calloc方法最后调用的segregated_size_to_fit方法中:

static MALLOC_INLINE size_t
segregated_size_to_fit(nanozone_t *nanozone, size_t size, size_t *pKey)
{
    size_t k, slot_bytes;

    if (0 == size) {
        size = NANO_REGIME_QUANTA_SIZE; // Historical behavior
    }
    k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM; // round up and shift for number of quanta
    slot_bytes = k << SHIFT_NANO_QUANTUM;                           // multiply by power of two quanta size
    *pKey = k - 1;                                                  // Zero-based!

    return slot_bytes;
}

size为传入的40,NANO_REGIME_QUANTA_SIZE为16, SHIFT_NANO_QUANTUM为4。
slot_bytes就是返回的最终的对象大小,其算法为: (40 + 16 - 1) >> 4 << 4,即对40进行16字节对齐,最终结果为48。

16字节对齐的原因:若以8字节对齐,64字节以内,可以存放8个8字节对象(8,16,24,32,40,48,56,64),对象之间两两相邻。若以16字节对齐,64字节内可以放4个对象(16,32,48,64),对象两两相邻的次数降低,减少系统误指的概率。另一方面,一个对象的成员变量最小为8(isa),但只含有默认变量isa的对象概率极低,实际使用中绝大部分类都有额外的成员变量,若以8字节对齐则每个对象内存都要做额外计算,因此直接使用16字节对齐会提高效率。

你可能感兴趣的:(iOS底层之内存对齐算法解析)