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

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底层内存优化-引用计数的存储)