美文网首页iOS进阶
iOS底层内存优化-引用计数的存储

iOS底层内存优化-引用计数的存储

作者: Jey | 来源:发表于2020-12-29 16:07 被阅读0次

    OC中各种变量的存储内存地址

    截屏2020-12-29 上午11.38.14.png

    // 栈区

    int i = 10;
    int j = 10;
    NSObject* obj = [NSObject new];
            
    NSLog(@"%p", &i);
    NSLog(@"%p", &j);
    NSLog(@"%p", &obj);
    2020-12-29 14:46:34.444711+0800 Performace001[42044:666158] 0x7ffee87abe0c
    2020-12-29 14:46:34.445492+0800 Performace001[42044:666158] 0x7ffee87abe08
    2020-12-29 14:46:34.445581+0800 Performace001[42044:666158] 0x7ffee87abe00
    

    堆区

    NSObject* obj1 = [NSObject new];
    NSObject* obj2 = [NSObject new];
    NSObject* obj3 = [NSObject new];
    NSLog(@"%p", obj);
    NSLog(@"%p", obj1);
    NSLog(@"%p", obj2);
    NSLog(@"%p", obj3);
    2020-12-29 14:46:34.445638+0800 Performace001[42044:666158] 0x600002cf40e0
    2020-12-29 14:46:34.445702+0800 Performace001[42044:666158] 0x600002cf0000
    2020-12-29 14:46:34.445755+0800 Performace001[42044:666158] 0x600002cf0010
    2020-12-29 14:46:34.445831+0800 Performace001[42044:666158] 0x600002cf0020        
    

    系统针对小对象做的内存优化,Tagged Pointer

    Tagged Pointer专门用来存储晓得对象,例如:NSNumber和NSDate.
    Tagged Pointer指针的值不再是地址,而是真正的值。所以他不是一个对象,是一个披着对象皮的普通变量而已。所以它不存储在堆,也不需要alloc和释放。
    直接在栈读取,速度快。

    for (int i = 0; i < 10; i++) {
         NSNumber* num = @(i*1.0f);
         NSLog(@"%p", num);
    }
    
    2020-12-29 14:35:43.755619+0800 Performance002[41600:649989] 0xb34e05bf934add54
    2020-12-29 14:35:43.756080+0800 Performance002[41600:649989] 0xb34e05bf934add44
    2020-12-29 14:35:43.756166+0800 Performance002[41600:649989] 0xb34e05bf934add74
    2020-12-29 14:35:43.756236+0800 Performance002[41600:649989] 0xb34e05bf934add64
    2020-12-29 14:35:43.756315+0800 Performance002[41600:649989] 0xb34e05bf934add14
    2020-12-29 14:35:43.756376+0800 Performance002[41600:649989] 0xb34e05bf934add04
    2020-12-29 14:35:43.756428+0800 Performance002[41600:649989] 0xb34e05bf934add34
    2020-12-29 14:35:43.756472+0800 Performance002[41600:649989] 0xb34e05bf934add24
    2020-12-29 14:35:43.756535+0800 Performance002[41600:649989] 0xb34e05bf934addd4
    2020-12-29 14:35:43.756633+0800 Performance002[41600:649989] 0xb34e05bf934addc4
    
    大对象例子:
    for (int i = 0; i < 10; i++) {
          NSNumber* num = @(i*0xFFFFFFFFFFFFFFF);
          NSLog(@"%p", num);
     }
    2020-12-29 14:38:05.773636+0800 Performance002[41665:651691] 0xa45d2595b2beb8b5
    2020-12-29 14:38:05.774045+0800 Performance002[41665:651691] 0x60000284dc60
    2020-12-29 14:38:05.774148+0800 Performance002[41665:651691] 0x600002864000
    2020-12-29 14:38:05.774242+0800 Performance002[41665:651691] 0x600002848080
    2020-12-29 14:38:05.774306+0800 Performance002[41665:651691] 0x6000028681e0
    2020-12-29 14:38:05.774367+0800 Performance002[41665:651691] 0x60000286dc80
    2020-12-29 14:38:05.774463+0800 Performance002[41665:651691] 0x600002860000
    2020-12-29 14:38:05.774523+0800 Performance002[41665:651691] 0x600002864000
    2020-12-29 14:38:05.774585+0800 Performance002[41665:651691] 0x600002848080
    2020-12-29 14:38:05.774644+0800 Performance002[41665:651691] 0x600002860000
    

    0x6开头是在堆区了

    再来个:

    NSString* s = @"123";
    NSString* s1 = [NSString stringWithFormat:@"123"];
    NSString* s2 = [NSString stringWithFormat:@"12344556679"];
    NSLog(@"s = %p \n s1 = %p \n s2 = %p \n", s, s1, s2);
    
    2020-12-29 14:40:39.794167+0800 Performance002[41734:653838] s = 0x10d178088 
     s1 = 0xcadc0920647f9d1f 
     s2 = 0x600003b300c0
    

    系统判断:s的地址是在常量区,s1是一个值存在栈区,s2是堆区

    引用计数

    isa_t

    isa_t是一个联合体,uintptr_t bits有64位

    union isa_t 
    {
        isa_t() { }
        isa_t(uintptr_t value) : bits(value) { }
    
        Class cls;
        uintptr_t bits;
    
    #if SUPPORT_PACKED_ISA
    
        // extra_rc must be the MSB-most field (so it matches carry/overflow flags)
        // nonpointer must be the LSB (fixme or get rid of it)
        // shiftcls must occupy the same bits that a real class pointer would
        // bits + RC_ONE is equivalent to extra_rc + 1
        // RC_HALF is the high bit of extra_rc (i.e. half of its range)
    
        // future expansion:
        // uintptr_t fast_rr : 1;     // no r/r overrides
        // uintptr_t lock : 2;        // lock for atomic property, @synch
        // uintptr_t extraBytes : 1;  // allocated with extra bytes
    
    # if __arm64__
    #   define ISA_MASK        0x0000000ffffffff8ULL
    #   define ISA_MAGIC_MASK  0x000003f000000001ULL
    #   define ISA_MAGIC_VALUE 0x000001a000000001ULL  
      // 位域
        struct {
            uintptr_t nonpointer        : 1;   // 数字代表占多少位
            uintptr_t has_assoc         : 1;
            uintptr_t has_cxx_dtor      : 1;
            uintptr_t shiftcls          : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
            uintptr_t magic             : 6;
            uintptr_t weakly_referenced : 1;
            uintptr_t deallocating      : 1;
            uintptr_t has_sidetable_rc  : 1; 
            uintptr_t extra_rc          : 19;
    #       define RC_ONE   (1ULL<<45)
    #       define RC_HALF  (1ULL<<18)
        };
    
    # elif __x86_64__
    #   define ISA_MASK        0x00007ffffffffff8ULL
    #   define ISA_MAGIC_MASK  0x001f800000000001ULL
    #   define ISA_MAGIC_VALUE 0x001d800000000001ULL
        struct {
            uintptr_t nonpointer        : 1; 
            uintptr_t has_assoc         : 1;
            uintptr_t has_cxx_dtor      : 1;
            uintptr_t shiftcls          : 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000
            uintptr_t magic             : 6;
            uintptr_t weakly_referenced : 1;
            uintptr_t deallocating      : 1;
            uintptr_t has_sidetable_rc  : 1;
            uintptr_t extra_rc          : 8;
    #       define RC_ONE   (1ULL<<56)
    #       define RC_HALF  (1ULL<<7)
        };
    
    # else
    #   error unknown architecture for packed isa
    # endif
    
    // SUPPORT_PACKED_ISA
    #endif
    
    
    #if SUPPORT_INDEXED_ISA
    
    # if  __ARM_ARCH_7K__ >= 2
    
    #   define ISA_INDEX_IS_NPI      1
    #   define ISA_INDEX_MASK        0x0001FFFC
    #   define ISA_INDEX_SHIFT       2
    #   define ISA_INDEX_BITS        15
    #   define ISA_INDEX_COUNT       (1 << ISA_INDEX_BITS)
    #   define ISA_INDEX_MAGIC_MASK  0x001E0001
    #   define ISA_INDEX_MAGIC_VALUE 0x001C0001
        struct {
            uintptr_t nonpointer        : 1;
            uintptr_t has_assoc         : 1;
            uintptr_t indexcls          : 15;
            uintptr_t magic             : 4;
            uintptr_t has_cxx_dtor      : 1;
            uintptr_t weakly_referenced : 1;
            uintptr_t deallocating      : 1;
            uintptr_t has_sidetable_rc  : 1;
            uintptr_t extra_rc          : 7;
    #       define RC_ONE   (1ULL<<25)
    #       define RC_HALF  (1ULL<<6)
        };
    
    # else
    #   error unknown architecture for indexed isa
    # endif
    
    // SUPPORT_INDEXED_ISA
    #endif
    
    };
    
    截屏2020-12-29 下午3.33.58.png

    arm64是手机的,struct刚好有64位,对应分别存储一些东西。
    cls 变量会指向对象所属的类的结构,在 64 位设备上会占用 8byte。8字节64位,每一位都对应值的作用,可以省内存。

    这部分是存储引用计数的位

    uintptr_t has_sidetable_rc  : 1;
    uintptr_t extra_rc          : 19;
    

    retaincount函数

    inline uintptr_t 
    objc_object::rootRetainCount()
    {
        if (isTaggedPointer()) return (uintptr_t)this;
    
        sidetable_lock();
        isa_t bits = LoadExclusive(&isa.bits);
        ClearExclusive(&isa.bits);
        if (bits.nonpointer) {
            uintptr_t rc = 1 + bits.extra_rc;
            if (bits.has_sidetable_rc) {
                rc += sidetable_getExtraRC_nolock();
            }
            sidetable_unlock();
            return rc;
        }
    
        sidetable_unlock();
        return sidetable_retainCount();
    }
    

    如果 if (isTaggedPointer()) return (uintptr_t)this;,就不使用引用计数,直接返回。
    如果 if (bits.has_sidetable_rc)散列表有值,就加上。

    散列表,SideTable,结构体

    struct SideTable {
        spinlock_t slock;
        RefcountMap refcnts; // 散列表,存储引用计数
        weak_table_t weak_table;
    
        SideTable() {
            memset(&weak_table, 0, sizeof(weak_table));
        }
    
        ~SideTable() {
            _objc_fatal("Do not delete SideTable.");
        }
    
        void lock() { slock.lock(); }
        void unlock() { slock.unlock(); }
        void forceReset() { slock.forceReset(); }
    
        // Address-ordered lock discipline for a pair of side tables.
    
        template
        static void lockTwo(SideTable *lock1, SideTable *lock2);
        template
        static void unlockTwo(SideTable *lock1, SideTable *lock2);
    };
    

    Object的retain

    ALWAYS_INLINE id 
    objc_object::rootRetain(bool tryRetain, bool handleOverflow)
    {
        if (isTaggedPointer()) return (id)this;
    
        bool sideTableLocked = false;
        bool transcribeToSideTable = false;
    
        isa_t oldisa;
        isa_t newisa;
    
        do {
            transcribeToSideTable = false;
            oldisa = LoadExclusive(&isa.bits);
            newisa = oldisa;
            if (slowpath(!newisa.nonpointer)) {
                ClearExclusive(&isa.bits);
                if (!tryRetain && sideTableLocked) sidetable_unlock();
                if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
                else return sidetable_retain();
            }
            // don't check newisa.fast_rr; we already called any RR overrides
            if (slowpath(tryRetain && newisa.deallocating)) {
                ClearExclusive(&isa.bits);
                if (!tryRetain && sideTableLocked) sidetable_unlock();
                return nil;
            }
            uintptr_t carry;
            newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++
    
            if (slowpath(carry)) {
                // newisa.extra_rc++ overflowed
                if (!handleOverflow) {
                    ClearExclusive(&isa.bits);
                    return rootRetain_overflow(tryRetain);
                }
                // Leave half of the retain counts inline and 
                // prepare to copy the other half to the side table.
                if (!tryRetain && !sideTableLocked) sidetable_lock();
                sideTableLocked = true;
                transcribeToSideTable = true;
                newisa.extra_rc = RC_HALF;
                newisa.has_sidetable_rc = true;
            }
        } while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));
    
        if (slowpath(transcribeToSideTable)) {
            // Copy the other half of the retain counts to the side table.
            sidetable_addExtraRC_nolock(RC_HALF);
        }
    
        if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
        return (id)this;
    }
    

    引用计数的++:newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
    这个时候可能溢出的情况,>2^19

     // Leave half of the retain counts inline and 
                // prepare to copy the other half to the side table.
                if (!tryRetain && !sideTableLocked) sidetable_lock();
                sideTableLocked = true;
                transcribeToSideTable = true;
                newisa.extra_rc = RC_HALF;
                newisa.has_sidetable_rc = true;
    

    newisa.extra_rc = RC_HALF;这句是把extra_rc取一半2^18,然后另一半拷贝入散列表sideTable

     if (slowpath(transcribeToSideTable)) {
            // Copy the other half of the retain counts to the side table.
            sidetable_addExtraRC_nolock(RC_HALF);
        }
    
    release

    extra_rc-- 可能发生下溢出,就会去散列表中取回来

    总结: 截屏2020-12-29 下午2.16.09.png

    weak实现原理

    在SideTable中,weak_table_t weak_table;是一个散列表。
    弱引用对象,底层也是使用散列表存储,对象的内存地址作为key,指向该对象的所有弱引用的指针作为值。

    释放时


    截屏2020-12-29 下午4.26.11.png
    截屏2020-12-29 下午4.38.16.png 截屏2020-12-29 下午4.38.05.png

    调用cleardeallocating函数,找到SideTable,通过isa.weakly_referenced判断是否有弱引用表,weak_table是一个弱引用散列表,以对象referent_id的内存地址作为key,去存储弱引用对象的哈希表中entry,遍历entry指向的referrers找到所有的弱引用对象,置为nil,最后移除弱引用散列表。

    截屏2020-12-29 下午4.41.37.png

    相关文章

      网友评论

        本文标题:iOS底层内存优化-引用计数的存储

        本文链接:https://www.haomeiwen.com/subject/tcsfoktx.html