iOS SideTable

作者: 赵哥窟 | 来源:发表于2020-01-20 10:26 被阅读0次
    SideTable主要存放了OC对象的引用计数和弱引用相关信息。定义如

    下:

    struct SideTable {
        spinlock_t slock;           // 自旋锁,防止多线程访问冲突
        RefcountMap refcnts;        // 对象引用计数map
        weak_table_t weak_table;    // 对象弱引用map
    
        SideTable() {
            memset(&weak_table, 0, sizeof(weak_table));
        }
    
        ~SideTable() {
            _objc_fatal("Do not delete SideTable.");
        }
    
        // 锁操作 符合StripedMap对T的定义
        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);
    };
    
    SideTable的定义很清晰有三个成员:

    ●spinlock_t slock : 自旋锁,用于上锁/解锁 SideTable。
    ●RefcountMap refcnts :以DisguisedPtr<objc_object>为key的hash表,用来存储OC对象的引用计数(仅在未开启isa优化 或 在isa优化情况下isa_t的引用计数溢出时才会用到)。
    ●weak_table_t weak_table : 存储对象弱引用指针的hash表。是OC weak功能实现的核心数据结构。

    除了三个成员外,苹果为SideTable还写了构造和析构函数:

    // 构造函数
        SideTable() {
            memset(&weak_table, 0, sizeof(weak_table));
        }
    
        //析构函数(看看函数体,苹果设计的SideTable其实不希望被析构,不然会引起fatal 错误)
        ~SideTable() {
            _objc_fatal("Do not delete SideTable.");
      }
    

    通过析构函数可以知道,SideTable是不能被析构的。

    最后是一堆锁的操作,用于多线程访问SideTable, 同时,也符合我们上面提到的StripedMap中关于value的lock接口定义:

    // 锁操作 符合StripedMap对T的定义
        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);
    
    顺便了解下SideTables

    SideTables是一个64个元素长度的hash数组,里面存储了SideTable。SideTables的hash键值就是一个对象obj的address。
    因此可以说,一个obj,对应了一个SideTable。但是一个SideTable,会对应多个obj。因为SideTable的数量只有64个,所以会有很多obj共用同一个SideTable。


    image.png

    先来说一下最外层的SideTables。SideTables可以理解为一个全局的hash数组,里面存储了SideTable类型的数据,其长度为64。

    SideTabls可以通过全局的静态函数获取:

    static StripedMap<SideTable>& SideTables() {
        return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
    }
    

    可以看到,SideTabls 实质类型为模板类型StripedMap

    StripedMap
    // StripedMap<T> is a map of void* -> T, sized appropriately 
    // for cache-friendly lock striping. 
    // For example, this may be used as StripedMap<spinlock_t>
    // or as StripedMap<SomeStruct> where SomeStruct stores a spin lock.
    template<typename T>
    class StripedMap {
    
        enum { CacheLineSize = 64 };
    
    #if TARGET_OS_EMBEDDED
        enum { StripeCount = 8 };
    #else
        enum { StripeCount = 64 };  // iOS 设备的StripeCount = 64
    #endif
    
        struct PaddedT {
            T value alignas(CacheLineSize); // T value 64字节对齐
            
        };
    
        PaddedT array[StripeCount]; // 所有PaddedT struct 类型数据被存储在array数组中。iOS 设备 StripeCount == 64
    
        static unsigned int indexForPointer(const void *p) { // 该方法以void *作为key 来获取void *对应在StripedMap 中的位置
            uintptr_t addr = reinterpret_cast<uintptr_t>(p);
            return ((addr >> 4) ^ (addr >> 9)) % StripeCount; // % StripeCount 防止index越界
        }
    
     public:
        // 取值方法 [p],
        T& operator[] (const void *p) { 
            return array[indexForPointer(p)].value; 
        }
        const T& operator[] (const void *p) const { 
            return const_cast<StripedMap<T>>(this)[p]; 
        }
    
        
        // Shortcuts for StripedMaps of locks.
        void lockAll() {
            for (unsigned int i = 0; i < StripeCount; i++) {
                array[i].value.lock();
            }
        }
    
        void unlockAll() {
            for (unsigned int i = 0; i < StripeCount; i++) {
                array[i].value.unlock();
            }
        }
    
        void forceResetAll() {
            for (unsigned int i = 0; i < StripeCount; i++) {
                array[i].value.forceReset();
            }
        }
    
        void defineLockOrder() {
            for (unsigned int i = 1; i < StripeCount; i++) {
                lockdebug_lock_precedes_lock(&array[i-1].value, &array[i].value);
            }
        }
    
        void precedeLock(const void *newlock) {
            // assumes defineLockOrder is also called
            lockdebug_lock_precedes_lock(&array[StripeCount-1].value, newlock);
        }
    
        void succeedLock(const void *oldlock) {
            // assumes defineLockOrder is also called
            lockdebug_lock_precedes_lock(oldlock, &array[0].value);
        }
    
        const void *getLock(int i) {
            if (i < StripeCount) return &array[i].value;
            else return nil;
        }
    };
    

    可以知道, StripedMap 是一个以void *为hash key, T为vaule的hash 表。
    hash定位的算法如下:

    static unsigned int indexForPointer(const void *p) { // 该方法以void *作为key 来获取void *对应在StripedMap 中的位置
            uintptr_t addr = reinterpret_cast<uintptr_t>(p);
            return ((addr >> 4) ^ (addr >> 9)) % StripeCount; // % StripeCount 防止index越界
        }
    

    把地址指针右移4位异或地址指针右移9位,为什么这么做,也不用关心。我们只要关心重点是最后的值要取余StripeCount,来防止index越界就好。

    StripedMap的所有T类型数据都被封装到PaddedT中:

    struct PaddedT {
            T value alignas(CacheLineSize); // T value 64字节对齐
            
        };
    

    之所以再次封装到PaddedT (有填充的T)中,是为了字节对齐,估计是存取hash值时的效率考虑。

    接下来,这些PaddedT被放到数组array中:

    PaddedT array[StripeCount]; // 所有PaddedT struct 类型数据被存储在array数组中。iOS 设备 StripeCount == 64
    

    然后,苹果为array数组写了一些公共的存取数据的方法,主要是调用indexForPointer方法,使得外部传入的对象地址指针直接hash到对应的array节点:

    // 取值方法 [p],
        T& operator[] (const void *p) { 
            return array[indexForPointer(p)].value; 
        }
        const T& operator[] (const void *p) const { 
            return const_cast<StripedMap<T>>(this)[p]; 
        }
    

    接下来是一堆锁的操作,由于SideTabls是一个全局的hash表,因此当然必须要带锁访问。StripedMap提供了一些便捷的锁操作方法:

    // Shortcuts for StripedMaps of locks.
        void lockAll() {
            for (unsigned int i = 0; i < StripeCount; i++) {
                array[i].value.lock();
            }
        }
    
        void unlockAll() {
            for (unsigned int i = 0; i < StripeCount; i++) {
                array[i].value.unlock();
            }
        }
    
        void forceResetAll() {
            for (unsigned int i = 0; i < StripeCount; i++) {
                array[i].value.forceReset();
            }
        }
    
        void defineLockOrder() {
            for (unsigned int i = 1; i < StripeCount; i++) {
                lockdebug_lock_precedes_lock(&array[i-1].value, &array[i].value);
            }
        }
    
        void precedeLock(const void *newlock) {
            // assumes defineLockOrder is also called
            lockdebug_lock_precedes_lock(&array[StripeCount-1].value, newlock);
        }
    
        void succeedLock(const void *oldlock) {
            // assumes defineLockOrder is also called
            lockdebug_lock_precedes_lock(oldlock, &array[0].value);
        }
    
        const void *getLock(int i) {
            if (i < StripeCount) return &array[i].value;
            else return nil;
        }
    

    可以看到,所有的StripedMap锁操作,最终是调用的array[i].value的相关操作。因此,对于模板的抽象数据T类型,必须具备相关的lock操作接口。

    因此,要用StripedMap作为模板hash表,对于T类型还是有所要求的。而在SideTables中,T即为SideTable类型,我们稍后会看到SideTable是如何符合StripedMap的数据类型要求的。

    相关文章

      网友评论

        本文标题:iOS SideTable

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