iOS weak 底层实现原理(二):StripedMap

上篇: iOS weak 底层实现原理(一):DisguisedPtr

摘要

首先 StripedMap 是一个模版类型:template<typename T> class StripedMap,阅读其内部实现然后从数据结构角度看的话,它其实是作为一个 Keyvoid *ValueThash 表来用的。我们使用的 SideTables 对应类型是: StripedMap<SideTable>,阅读代码时我们能发现多处类似: SideTable *table = &SideTables()[obj] 这样的调用,它的作用正是根据对象的指针从 SideTables 这张 hash 表中找到 obj 所对应的 SideTableStripedMap 代码的实现并不复杂,下面一起来看下吧:

StripedMap 源码

// StripedMap<T> is a map of void* -> T, 
// sized appropriately for cache-friendly lock striping. 
// StripedMap<T> 是一个 void * -> T 的 map,
// 即 Key 为 void *,value 是 T 的 hash 表。

// For example, this may be used as StripedMap<spinlock_t> 
// or as StripedMap<SomeStruct> where SomeStruct stores a spin lock.
// 例如,它可能用作 StripedMap<spinlock_t> 或者 StripedMap<SomeStruct>,
// 此时 SomeStruct 保存了一个 spin lock.

// 它的主要功能就是把自旋锁的锁操作从类中分离出来,
// 而且类中必须要有一个自旋锁属性。(?)
// StripMap 内部实质是一个开放寻址法生成哈希键值的哈希表.

enum { CacheLineSize = 64 };

template<typename T>
class StripedMap {
// 针对程序运行不同平台,定义下面 array 的长度
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
    enum { StripeCount = 8 };
#else
    enum { StripeCount = 64 };
#endif

    struct PaddedT {
        // CacheLineSize 值为定值 64
        // T value 64 字节对齐
        // 结构体只有一个 T 类型的 value 成员变量
        // 且这个 value 是遵循 64 字节内存对齐原则
        // 这个是保证每个 SideTable 64 个字节内存占用 
        T value alignas(CacheLineSize);
    };

    // 所有 PaddedT struct 类型数据被存储在 array 数组中
    // StripeCount == 8/64
    // 长度为 8/64 的 PaddedT 数组
    // 其实是 hash 表数据,正是我们的 8/64 张 SideTable 
    PaddedT array[StripeCount];

    // 该方法以 void * 作为 key 来
    // 获取 void * 对应在 StripedMap 的 array 中的位置
    // hash 函数
    static unsigned int indexForPointer(const void *p) {
        // typedef unsigned long uintptr_t;
        uintptr_t addr = reinterpret_cast<uintptr_t>(p);

        // addr 右移 4 位的值与 addr 右移 9 位的值做异或操作,
        // 然后对 StripeCount 取模 
        // 最后取模,防止 index 越界
        // 保证返回值在 [0, 7] 或 [0, 63] 区间内
        return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
    }

 public:
    // 根据指针 p 得出 index,返回 array 数组 index 
    // 处的 PaddedT 的 value 成员变量
    // 操作符重载
    // 即可以使用 StripMap<xxx>[objcPtr] 访问 
    // 即可以使用 SideTables[p] 得到我们的对象所在 SideTable
    T& operator[] (const void *p) { 
        return array[indexForPointer(p)].value; 
    }

    // const_cast 该运算符用来修改类型的 const 或volatile属性。
    // 除了 const 或volatile 修饰之外, 
    // type_id 和 expression的类型是一样的。
    // 一、常量指针被转化成非常量的指针,并且仍然指向原来的对象;
    // 二、常量引用被转换成非常量的引用,并且仍然指向原来的对象;
    // 三、const_cast一般用于修改底指针。如const char *p形式。
    // -- 以上来自百度百科

    // 调用上面的 [],得到 T&
    // 然后转为 StripedMap<T>
    const T& operator[] (const void *p) const { 
        return const_cast<StripedMap<T>>(this)[p]; 
    }

    // Shortcuts for StripedMaps of locks.
    // StripedMaps 中锁的便捷操作:
    // 循环给 array 中的元素的 value 加锁
    void lockAll() {
        for (unsigned int i = 0; i < StripeCount; i++) {
            array[i].value.lock();
        }
    }

    // 循环给 array 中的元素的 value 解锁
    void unlockAll() {
        for (unsigned int i = 0; i < StripeCount; i++) {
            array[i].value.unlock();
        }
    }

    // 循环 array 中的元素的 value 执行 forceReset
    // 强制重置吗?
    void forceResetAll() {
        for (unsigned int i = 0; i < StripeCount; i++) {
            array[i].value.forceReset();
        }
    }

    // 对 array 中元素的 value 的 lock 定义锁定顺序?
    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
        // 假定 defineLockOrder 函数已经被调用过
        lockdebug_lock_precedes_lock(&array[StripeCount-1].value, newlock);
    }

    void succeedLock(const void *oldlock) {
        // assumes defineLockOrder is also called
        // 假定 defineLockOrder 函数已经被调用过
        lockdebug_lock_precedes_lock(oldlock, &array[0].value);
    }

    const void *getLock(int i) {
        if (i < StripeCount) return &array[i].value;
        else return nil;
    }

#if DEBUG
    StripedMap() {
        // Verify alignment expectations.
        // 验证是不是按 CacheLineSize(值为 64)个字节内存对齐的

        uintptr_t base = (uintptr_t)&array[0].value;
        uintptr_t delta = (uintptr_t)&array[1].value - base;
        ASSERT(delta % CacheLineSize == 0);
        ASSERT(base % CacheLineSize == 0);
    }
#else
    constexpr StripedMap() {}
#endif
};
复制代码

reinterpret_cast

延展: reinterpret_castC++ 里的强制类型转换符。此处的用法是把指针转化为一个整数。

reinterpret_cast<new_type> (expression)
复制代码

hash 函数

hash 定位的算法,把 void * 指针转化为整数,然后右移 4 位和右移 9 位的值做异或操作,然后对 StripedMap(值为 8/64) 取模,防止 index 越界,保证返回值在 [0, 7]/[0, 63] 区间之间。

// 该方法以 void * 作为 key 来获取 void * 
// 对应在 StripedMap 的 array 中的索引
static unsigned int indexForPointer(const void *p) {
    // typedef unsigned long uintptr_t;
    // 把 void * 指针转化为 unsigned long
    uintptr_t addr = reinterpret_cast<uintptr_t>(p);

    // addr 右移 4 位的值与 addr 右移 9 位的值做异或操作,然后对 StripeCount 取模 
    return ((addr >> 4) ^ (addr >> 9)) % StripeCount; // 最后取余,防止 index 越界
}
复制代码

数据封装 PaddedT

StripedMap 中的 T 类型数据作为结构体仅有的成员变量 T value 放在了 struct PaddedT 中,且指明 value 遵循 64 字节内存对齐,即为每张 SideTable 分配 64 字节内存。

struct PaddedT {
    // CacheLineSize 值为定值 64
    // T value 64 字节对齐
    // 表示结构体只有一个 T 类型的 value 成员变量
    // 且指明这个 value 是遵循 64 字节内存对齐原则
    T value alignas(CacheLineSize);
};
复制代码

之所以再次封装数据到 PaddedT 中,是为了字节对齐,估计是存取 hash 值时的效率考虑。 接下来 struct PaddedT 被放在数组 array 中:

// 所有 struct PaddedT 类型数据被存储在 array 数组中
// TARGET_OS_IPHONE 设备且非模拟器的情况下 StripeCount == 8
// 长度为 8 的 PaddedT 数组
PaddedT array[StripeCount];
复制代码

array 中取数据(SideTable

接下来是从 array 数组公共的取值方法,(此处是很奇怪的,只是定义了取值函数,并没有定义构造函数。)主要调用 indexForPointer 函数,使得外部传入的 对象地址指针 通过 hash 函数得到其在 array 数组中的索引,然后返回该索引元素的 value 值,即返回对应的 SideTable

// 根据指针 p 得出 index,返回 array 数组 index 
// 处的 PaddedT 的 value 成员变量
T& operator[] (const void *p) { 
    return array[indexForPointer(p)].value; 
}
const T& operator[] (const void *p) const { 
    return const_cast<StripedMap<T>>(this)[p]; 
}
复制代码

Lock 操作

接下来是一系列锁的操作,循环加锁、循环解锁、定义锁定顺序等等,由于 SideTables 是一个全局的 hash 表,因此必须要带锁访问。通过源码看到所有的 StripedMap 锁操作,最终调用的都是 array[i].value 的相关操作,而 value 是模版的抽象数据 T 类型,因此 T 必须具备相关的 lock 操作接口。在讲 《iOS weak 底层实现原理(四):SideTables和SideTable》一节中我们都能看到对应的实现。

因此,要用 StripedMap 作为模版 hash 表,对于 T 类型是有要求的,而在 SideTablesT 即为 SideTable 类型,等下我们会看 SideTable 是怎么符合 StripedMap 的数据类型要求的。

结尾

分析完 StripedMap 就分析完了 SideTables 这个全局的大 hash 表,下面我们继续来分析 SideTables 中存储的数据: SideTable

参考链接

参考链接:🔗

作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS交流群:761407670 进群密码000,不管你是小白还是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!

原文地址:https://juejin.im/post/6865585757688430606

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343