OC底层原理三:alloc流程

2020-09-21  本文已影响0人  赤耳诺

oc底层原理一里我们探索了如何定位底层源码的三种方式,在oc底层原理二里我们配置了objc4-781 的可编译环境,让我们更直观的探索底层流程。

可直接下载编译成功的objc4-781

本文就在以上两篇的基础上,探究下alloc的实现流程。

首先打开配置好的objc,利用符号断点的方式走一下alloc的整个流程,流程图如下:

alloc流程图
一步步走下来,我们会发现_class_createInstanceFromZone方法是alloc流程的核心实现:
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
                              int construct_flags = OBJECT_CONSTRUCT_NONE,
                              bool cxxConstruct = true,
                              size_t *outAllocatedSize = nil)
{
    ASSERT(cls->isRealized());

    // Read class's info bits all at once for performance
    //读取类的信息
    bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();
    size_t size;
    //询问需要开辟的内存大小,extraBytes为0
    size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (zone) {
        obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
        obj = (id)calloc(1, size);
    }
    if (slowpath(!obj)) {
        if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
            return _objc_callBadAllocHandler(cls);
        }
        return nil;
    }
    //将类和isa指针关联
    if (!zone && fast) {
        obj->initInstanceIsa(cls, hasCxxDtor);
    } else {
        // Use raw pointer isa on the assumption that they might be
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
    }

    if (fastpath(!hasCxxCtor)) {
        return obj;
    }

    construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
    return object_cxxConstructFromClass(obj, cls, construct_flags);
}

_class_createInstanceFromZone方法创建实例内存空间,主要有三部分实现:
- cls->instanceSize:计算需要开辟的内存空间大小
- calloc:申请内存,返回地址指针
- obj->initInstanceIsa:将类与isa关联

alloc核心方法

instanceSize源码
size_t instanceSize(size_t extraBytes) const {
        //快速计算内存大小
        if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
            return cache.fastInstanceSize(extraBytes);
        }
        //计算isa和成员变量需要的内存大小
        size_t size = alignedInstanceSize() + extraBytes;
        //如果不足16字节的,补齐16字节
        if (size < 16) size = 16;
        return size;
    }

首先就是有个fastpath的判断,判断是否需要编译器优化。根据调试可以从缓存中读取所需开辟空间大小cache.fastInstanceSize

size_t fastInstanceSize(size_t extra) const
    {
        ASSERT(hasFastInstanceSize(extra));
        //判断extra是否在编译时就可以确定其为常量,如果extra为常量,该函数返回1,否则返回0。
        //如果extra为常量,可以在代码中做一些优化来减少处理extra的复杂度。
        if (__builtin_constant_p(extra) && extra == 0) {
            return _flags & FAST_CACHE_ALLOC_MASK16;
        } else {
            size_t size = _flags & FAST_CACHE_ALLOC_MASK;
            // remove the FAST_CACHE_ALLOC_DELTA16 that was added
            // by setFastInstanceSize
            //十六字节对齐
            return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
        }
    }

builtin函数是GCC提供的,可以实现一些简单快捷的功能来方便程序编写,另外,很多builtin函数可用来优化编译结果。
__builtin_constant_p (extra):判断extra是否在编译时就可以确定其为常量,如果extra为常量,该函数返回1,否则返回0。如果extra为常量,可以在代码中做一些优化来减少处理extra的复杂度。

align16(size + extra - FAST_CACHE_ALLOC_DELTA16)通过字面就可以理解16字节对齐,看下源码:

static inline size_t align16(size_t x) {
    return (x + size_t(15)) & ~size_t(15);
}

size_t获取sizeof计算的数据类型,保存的一般是个整数。

我们以align(8)为例看下16字节对齐算法:
实际算法为(8 + size_t(15)) & ~size_t(15),即(8+15)&~15,即23&~15,二进制表示0x 0001 0111 & 0x 1111 0000,与运算相同的为1,不同的为0,所以我们知道这其实就是对16位以下清零的操作,最终计算为0x 0001 0000,即16。

为什么要16字节对齐呢?

我们都知道内存是由一个个字节组成的,但cpu在读取的时候不会按照字节去读取,cpu把内存当成是一块一块的,块的大小可以是2,4,8,16 个字节,因此CPU在读取内存的时候是一块一块进行读取的,块的大小称为内存读取粒度。

假设CPU要读取一个8字节大小的数据到寄存器中,如果数据从0字节开始,直接将0-7四个字节完全读取到寄存器。但如果数据从1字节开始读取,首先先将0-7字节读到寄存器,并再次读取8-15字节的数据进寄存器,接着把0字节,9-15字节的数据剔除,最后合并1-8字节的数据进寄存器,做了这么多额外操作,大大降低了CPU的性能。

而对齐后则以空间换时间,大大提高了cpu的读取速度。

而我们知道每个对象都有个isa指针,isa指针占用8个字节,每一个属性也是占用8个字节。当无属性的时候每个对象最少占8个字节。如果用8字节对齐的话,对象和对象之间的isa就会连在一块,可能访问时就会出现问题。所以16字节对齐一方面解决了这样的隐患,而且也方便了以后的扩展。

calloc源码
void    *calloc(size_t __count, size_t __size) __result_use_check __alloc_size(1,2);

calloc在内存的动态存储区中分配count个长度为size的连续空间。num:属性个数+isa,size:instanceSize计算出的size。
分配好内存后,返回地址指针。

initIsa源码
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
    ASSERT(!isTaggedPointer()); 
    
    if (!nonpointer) {
        isa = isa_t((uintptr_t)cls);
    } else {
        ASSERT(!DisableNonpointerIsa);
        ASSERT(!cls->instancesRequireRawIsa());

        isa_t newisa(0);

#if SUPPORT_INDEXED_ISA
        ASSERT(cls->classArrayIndex() > 0);
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
        newisa.bits = ISA_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.shiftcls = (uintptr_t)cls >> 3;
#endif

        // This write must be performed in a single store in some cases
        // (for example when realizing a class because other threads
        // may simultaneously try to use the class).
        // fixme use atomics here to guarantee single-store and to
        // guarantee memory order w.r.t. the class index table
        // ...but not too atomic because we don't want to hurt instantiation
        isa = newisa;
    }
}

obj->initInstanceIsa初始化一个isa指针,指向这个对象,然后绑定这个对象。具体的绑定过程后面抽一篇具体研究。

上一篇下一篇

猜你喜欢

热点阅读