iOS底层探索-散列表

290 阅读3分钟

上一篇 内存管理 中我们了解到,对象的内存管理有tagged pointer这种无需引用计数的,也有需要引用计数的;而当 指针为非nonpointerIsa、或者nonpointerIsa中的 extra_rc 容量不够存放引用计数时,会使用 散列表sidetable 来协助存储,本篇就来探索一下 sidetable

sidetable(散列表)

struct SideTable {
    spinlock_t slock;
    // 存放从extra_rc接收的那一半引用计数
    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<HaveOld, HaveNew>
    static void lockTwo(SideTable *lock1, SideTable *lock2);
    template<HaveOld, HaveNew>
    static void unlockTwo(SideTable *lock1, SideTable *lock2);
};
  • 主要包含一把spinlock锁、引用计数表弱引用表

1、RefcountMap(引用计数表)

  • RefcountMap 本身从DenseMap得来,这个东西很眼熟,就是在探索 关联对象 时打过交道的,就是hash表

    typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,RefcountMapValuePurgeable> RefcountMap;
    
  • 存放从 extra_rc 接收的那一半引用计数

    if (variant == RRVariant::Full) {
        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();
    } else {
        ASSERT(!transcribeToSideTable);
        ASSERT(!sideTableLocked);
    }
    

    sidetable_addExtraRC_nolock

    bool 
    objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)
    {
        ASSERT(isa.nonpointer);
        // 获取SideTables,也就是StripeMap
        SideTable& table = SideTables()[this];
    
        size_t& refcntStorage = table.refcnts[this];
        size_t oldRefcnt = refcntStorage;
        // isa-side bits should not be set here
        ASSERT((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);
        ASSERT((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);
    
        if (oldRefcnt & SIDE_TABLE_RC_PINNED) return true;
    
        uintptr_t carry;
        size_t newRefcnt = 
            addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry);
        if (carry) {
            refcntStorage =
                SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK);
            return true;
        }
        else {
            refcntStorage = newRefcnt;
            return false;
        }
    }
    
  • 系统中使用 spinlock_t 加锁的内容,因为频繁读取操作会对性能产生影响,所以经常使用多张StripedMap来分散压力,且 StripedMap 数量根据系统数量固定,这是探索 @synchronized 是知道的

    // 上面 SideTables 的实现
    static StripedMap<SideTable>& SideTables() {
        return SideTablesMap.get();
    }
    

2、WeakTable(弱引用表)

弱引用底层会调用objc_initWeak

id
objc_initWeak(id *location, id newObj)
{
    if (!newObj) {
        *location = nil;
        return nil;
    }

    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}

2.1、storeWeak

enum CrashIfDeallocating {
    DontCrashIfDeallocating = false, DoCrashIfDeallocating = true
};

// HaveOld:weak指针是否指向一个弱引用
// HavNew:weak指针是否需要指向一个新的引用
template <HaveOld haveOld, HaveNew haveNew,
          enum CrashIfDeallocating crashIfDeallocating>
static id 
storeWeak(id *location, objc_object *newObj)
{
    ASSERT(haveOld  ||  haveNew);
    if (!haveNew) ASSERT(newObj == nil);

    Class previouslyInitializedClass = nil;
    id oldObj;
    SideTable *oldTable;
    SideTable *newTable;

    // Acquire locks for old and new values.
    // Order by lock address to prevent lock ordering problems. 
    // Retry if the old value changes underneath us.
 retry:
    if (haveOld) {
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (haveNew) {
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }

    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);

    if (haveOld  &&  *location != oldObj) {
        SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
        goto retry;
    }

    // Prevent a deadlock between the weak reference machinery
    // and the +initialize machinery by ensuring that no 
    // weakly-referenced object has an un-+initialized isa.
    if (haveNew  &&  newObj) {
        Class cls = newObj->getIsa();
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) 
        {
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            class_initialize(cls, (id)newObj);

            // If this class is finished with +initialize then we're good.
            // If this class is still running +initialize on this thread 
            // (i.e. +initialize called storeWeak on an instance of itself)
            // then we may proceed but it will appear initializing and 
            // not yet initialized to the check above.
            // Instead set previouslyInitializedClass to recognize it on retry.
            previouslyInitializedClass = cls;

            goto retry;
        }
    }

    // Clean up old value, if any.
    if (haveOld) {
        // 清除原来弱引用表中数据
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    // Assign new value, if any.
    if (haveNew) {
        newObj = (objc_object *)
            // 将weak的指针地址添加到对象的弱引用表
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);
        // weak_register_no_lock returns nil if weak store should be rejected

        // Set is-weakly-referenced bit in refcount table.
        if (!newObj->isTaggedPointerOrNil()) {
            // 将对象曾经指向过若引用的标识置为true,没有弱引用的释放更快
            newObj->setWeaklyReferenced_nolock();
        }

        // Do not set *location anywhere else. That would introduce a race.
        *location = (id)newObj;
    }
    else {
        // No new value. The storage is not changed.
    }

    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);

    // This must be called without the locks held, as it can invoke
    // arbitrary code. In particular, even if _setWeaklyReferenced
    // is not implemented, resolveInstanceMethod: may be, and may
    // call back into the weak reference machinery.
    callSetWeaklyReferenced((id)newObj);

    return (id)newObj;
}
  • storeWeak 一共有5个参数,其中3个参数定义在了 template模板,在探索类时我们也遇到过(property、method、protocol的模板)
  • weak_unregister_no_lock:清除原来弱引用表中数据
  • weak_register_no_lock:将weak的指针地址添加到对象的弱引用表

2.2、weak_entry_t

struct weak_table_t {
    // 弱引用数组
    weak_entry_t *weak_entries;
    // 数组个数
    size_t    num_entries;
    uintptr_t mask;
    uintptr_t max_hash_displacement;
};
  • weak_entries:哈希数组,一个对象可以被多个弱引用指针引用,因此这里是个数组
struct weak_entry_t {
    DisguisedPtr<objc_object> referent;
    // 联合体,对象的弱引用个数若小于等于4,下边的结构体:静态存储,大于4,上边的结构体:动态存储
    union {
        struct {
            weak_referrer_t *referrers;
            uintptr_t        out_of_line_ness : 2;
            uintptr_t        num_refs : PTR_MINUS_2;
            uintptr_t        mask;
            uintptr_t        max_hash_displacement;
        };
        struct {
            // out_of_line_ness field is low bits of inline_referrers[1]
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };

    bool out_of_line() {
        return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
    }

    weak_entry_t& operator=(const weak_entry_t& other) {
        memcpy(this, &other, sizeof(other));
        return *this;
    }

    weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
        : referent(newReferent)
    {
        inline_referrers[0] = newReferrer;
        for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
            inline_referrers[i] = nil;
        }
    }
};
  • 存储的内容是弱引用对象的指针地址
  • 对象的弱引用个数 <=4 走静态存储(在weak_entry_t初始化的时候一并分配好), >4 走动态存储

总结

  • SideTables 可以理解为一个全局的hash数组,里面存储了SideTable类型的数据,其长度为 8(或64)

  • 一个obj,对应了一个SideTable,但是一个SideTable,会对应多个obj。因为SideTable的数量只有8(或64)个所以会有很多obj共用同一个SideTable

  • 弱引用表中,key是对象的地址,value是weak指针地址的数组(weak_entry_t)

  • weak_unregister_no_lockweak_register_no_lock 中都是对 weak_entry_t 类型的数组进行操作

vj3qnhn76u.png